From acede480c55e42136dcb08a65f16ba4b1a6c958f Mon Sep 17 00:00:00 2001 From: Ramesh Sivaraman Date: Thu, 13 May 2021 15:31:58 +0300 Subject: Updated galera_3nodes disabled.def file --- mysql-test/suite/galera_3nodes/disabled.def | 1 - 1 file changed, 1 deletion(-) diff --git a/mysql-test/suite/galera_3nodes/disabled.def b/mysql-test/suite/galera_3nodes/disabled.def index 7c4d16f98f5..9100db4d441 100644 --- a/mysql-test/suite/galera_3nodes/disabled.def +++ b/mysql-test/suite/galera_3nodes/disabled.def @@ -15,7 +15,6 @@ galera_gtid_2_cluster : MDEV-23775 Galera test failure on galera_3nodes.galera_g galera_ist_gcache_rollover : MDEV-23578 WSREP: exception caused by message: {v=0,t=1,ut=255,o=4,s=0,sr=0,as=1,f=6,src=50524cfe,srcvid=view_id(REG,50524cfe,4),insvid=view_id(UNKNOWN,00000000,0),ru=00000000,r=[-1,-1],fs=75,nl=(} galera_load_data_ist : MDEV-24639 galera_3nodes.galera_load_data_ist MTR failed with SIGABRT: query 'reap' failed: 2013: Lost connection to MySQL server during query galera_load_data_ist : MDEV-24639 galera_3nodes.galera_load_data_ist MTR failed with SIGABRT: query 'reap' failed: 2013: Lost connection to MySQL server during query -galera_pc_bootstrap : MDEV-24650 galera_pc_bootstrap MTR failed: Could not execute 'check-testcase' before testcase galera_safe_to_bootstrap : MDEV-24097 galera_3nodes.galera_safe_to_bootstrap MTR sporadaically fails: Failed to start mysqld or mysql_shutdown failed galera_slave_options_do : MDEV-8798 galera_slave_options_ignore : MDEV-8798 -- cgit v1.2.1 From af8d4a97e29905f2806e7f26b420ce517e96c723 Mon Sep 17 00:00:00 2001 From: Sujatha Date: Tue, 18 May 2021 15:45:43 +0530 Subject: MDEV-22530: Aborting OPTIMIZE TABLE still logs in binary log and replicates to the Slave server. Post push fix to address test issue. --- mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result | 2 +- mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result b/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result index a2eb7ee5c0a..d9bfead8dd1 100644 --- a/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result +++ b/mysql-test/suite/binlog/r/binlog_admin_cmd_kill.result @@ -20,7 +20,7 @@ master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (f INT) ENGINE=INNODB master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; CREATE TABLE t2 (f INT) ENGINE=INNODB DROP TABLE t1,t2; -FLUSH LOGS; +RESET MASTER; # # Kill OPTIMIZE command after table modification # diff --git a/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test b/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test index 9b248097ae2..e2397f0eab8 100644 --- a/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test +++ b/mysql-test/suite/binlog/t/binlog_admin_cmd_kill.test @@ -56,7 +56,7 @@ SET debug_sync = 'reset'; --source include/show_binlog_events.inc DROP TABLE t1,t2; -FLUSH LOGS; +RESET MASTER; --echo # --echo # Kill OPTIMIZE command after table modification @@ -81,6 +81,9 @@ eval KILL $thd_id; SET debug_sync = 'reset'; --disconnect con1 +--let $wait_binlog_event= OPTIMIZE +--source include/wait_for_binlog_event.inc + DROP TABLE t1,t2; let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1); FLUSH LOGS; -- cgit v1.2.1 From 9ecf9a644c57be61a8f1399ce677d72f5865a770 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 13 May 2021 17:54:15 +0200 Subject: MDEV-25617 10.5.10 upgrade: "scriptlet / line 6 : [: is-active : binary operator expected" --- support-files/rpm/server-posttrans.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-files/rpm/server-posttrans.sh b/support-files/rpm/server-posttrans.sh index 313274c6140..d91ff65e04f 100644 --- a/support-files/rpm/server-posttrans.sh +++ b/support-files/rpm/server-posttrans.sh @@ -3,7 +3,7 @@ if [ -r %{restart_flag} ] ; then # only restart the server if it was already running if [ -x /usr/bin/systemctl ] ; then /usr/bin/systemctl daemon-reload > /dev/null 2>&1 - if [ /usr/bin/systemctl is-active mysql ]; then + if /usr/bin/systemctl is-active mysql; then /usr/bin/systemctl restart mysql > /dev/null 2>&1 else /usr/bin/systemctl try-restart mariadb.service > /dev/null 2>&1 -- cgit v1.2.1 From 9bbedcdd590f2c25c79bd9f367a7f54c1fcefdcb Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 19 May 2021 16:10:13 +0200 Subject: don't require jemalloc for 10.5 official packages it was only for TokuDB, and TokuDB isn't packaged in 10.5 --- cmake/build_configurations/mysql_release.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmake/build_configurations/mysql_release.cmake b/cmake/build_configurations/mysql_release.cmake index d905a81fce9..ed3b702b817 100644 --- a/cmake/build_configurations/mysql_release.cmake +++ b/cmake/build_configurations/mysql_release.cmake @@ -98,7 +98,6 @@ ELSEIF(RPM) SET(WITH_ZLIB system CACHE STRING "") SET(CHECKMODULE /usr/bin/checkmodule CACHE FILEPATH "") SET(SEMODULE_PACKAGE /usr/bin/semodule_package CACHE FILEPATH "") - SET(WITH_JEMALLOC "yes" CACHE STRING "") SET(PLUGIN_AUTH_SOCKET YES CACHE STRING "") SET(WITH_EMBEDDED_SERVER ON CACHE BOOL "") # not yet, SLES 12.3 doesn't provide pcre2 @@ -108,7 +107,6 @@ ELSEIF(DEB) SET(WITH_ZLIB system CACHE STRING "") SET(WITH_LIBWRAP ON) SET(HAVE_EMBEDDED_PRIVILEGE_CONTROL ON) - SET(WITH_JEMALLOC "yes" CACHE STRING "") SET(PLUGIN_AUTH_SOCKET YES CACHE STRING "") SET(WITH_EMBEDDED_SERVER ON CACHE BOOL "") SET(WITH_PCRE system CACHE STRING "") -- cgit v1.2.1 From e570f740cdabb1774683203ee848d522c6588dbc Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Fri, 14 May 2021 20:43:21 +0300 Subject: MDEV-25629: Crash in get_sort_by_table() in subquery with order by having outer ref In Item_field::fix_fields(): when the item was resolved to an Item_field in the SELECT's select_list, copy the Item_field's "depended_from" field. Failure to do so caused the item to have incorrect attributes: it pointed to a Field in an upper select but used_tables() didn't return OUTER_REF_TABLE_BIT. --- mysql-test/r/subselect4.result | 10 ++++++++++ mysql-test/t/subselect4.test | 14 ++++++++++++++ sql/item.cc | 1 + 3 files changed, 25 insertions(+) diff --git a/mysql-test/r/subselect4.result b/mysql-test/r/subselect4.result index 4021f717964..2a691799be5 100644 --- a/mysql-test/r/subselect4.result +++ b/mysql-test/r/subselect4.result @@ -2783,3 +2783,13 @@ INSERT INTO t2 VALUES (3),(4); SELECT 1 IN (SELECT (SELECT a FROM t1) AS x FROM t2 GROUP BY x); ERROR 21000: Subquery returns more than 1 row drop table t1,t2; +# +# MDEV-25629: Crash in get_sort_by_table() in subquery with order by having outer ref +# +CREATE TABLE t1 (i1 int); +insert into t1 values (1),(2); +SELECT 1 +FROM (t1 JOIN t1 AS ref_t1 ON +(t1.i1 > (SELECT ref_t1.i1 AS c0 FROM t1 b ORDER BY -c0))); +ERROR 21000: Subquery returns more than 1 row +DROP TABLE t1; diff --git a/mysql-test/t/subselect4.test b/mysql-test/t/subselect4.test index e218e3aab18..58aa7868815 100644 --- a/mysql-test/t/subselect4.test +++ b/mysql-test/t/subselect4.test @@ -2282,3 +2282,17 @@ INSERT INTO t2 VALUES (3),(4); # Optional, fails either way --error ER_SUBQUERY_NO_1_ROW SELECT 1 IN (SELECT (SELECT a FROM t1) AS x FROM t2 GROUP BY x); drop table t1,t2; + +--echo # +--echo # MDEV-25629: Crash in get_sort_by_table() in subquery with order by having outer ref +--echo # +CREATE TABLE t1 (i1 int); +insert into t1 values (1),(2); + +--error ER_SUBQUERY_NO_1_ROW +SELECT 1 +FROM (t1 JOIN t1 AS ref_t1 ON + (t1.i1 > (SELECT ref_t1.i1 AS c0 FROM t1 b ORDER BY -c0))); + +DROP TABLE t1; + diff --git a/sql/item.cc b/sql/item.cc index 42272fe0148..be64edca9a1 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5513,6 +5513,7 @@ bool Item_field::fix_fields(THD *thd, Item **reference) */ set_max_sum_func_level(thd, select); set_field(new_field); + depended_from= (*((Item_field**)res))->depended_from; return 0; } else -- cgit v1.2.1 From 406ce57232aac5c65bf0591078f36c4c56b19c95 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 19 May 2021 18:09:49 +1000 Subject: MDEV-25728: mysqld --help --verbose creates a log-bin-index file before change test: strace -fe trace=file -o /tmp/f.strace sql/mysqld --datadir=/tmp/d --log-bin=foo-bin --help --verbose && ls -la /tmp/ ... 'mysqladmin variables' instead of 'mysqld --verbose --help'. total 0 drwxrwxr-x. 2 dan dan 60 May 19 18:05 . drwxrwxrwt. 27 root root 640 May 19 18:03 .. -rw-rw----. 1 dan dan 0 May 19 18:05 foo-bin.index --- mysql-test/r/mysqld--help.result | 2 +- mysql-test/t/mysqld--help.test | 2 +- sql/mysqld.cc | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index 94f2ea0a28c..b24b9ee735f 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -1314,7 +1314,7 @@ lc-messages-dir MYSQL_SHAREDIR/ lc-time-names en_US local-infile TRUE lock-wait-timeout 86400 -log-bin (No default value) +log-bin foo log-bin-compress FALSE log-bin-compress-min-len 256 log-bin-index (No default value) diff --git a/mysql-test/t/mysqld--help.test b/mysql-test/t/mysqld--help.test index 12834b6177b..bc5d357a543 100644 --- a/mysql-test/t/mysqld--help.test +++ b/mysql-test/t/mysqld--help.test @@ -11,7 +11,7 @@ # force symbolic-links=0 (valgrind build has a different default) # -exec $MYSQLD_BOOTSTRAP_CMD --symbolic-links=0 --lower-case-table-names=1 --help --verbose > $MYSQL_TMP_DIR/mysqld--help.txt 2>&1; +exec $MYSQLD_BOOTSTRAP_CMD --symbolic-links=0 --log-bin=foo --lower-case-table-names=1 --help --verbose > $MYSQL_TMP_DIR/mysqld--help.txt 2>&1; # The inline perl code below will copy $MYSQL_TMP_DIR/mysqld--help.txt # to output, but filter away some variable stuff (e.g. paths). diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 48121ce2ea2..9afc701c6ba 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -5324,7 +5324,7 @@ static int init_server_components() } } - if (opt_bin_log) + if (!opt_help && opt_bin_log) { if (mysql_bin_log.open_index_file(opt_binlog_index_name, opt_bin_logname, TRUE)) @@ -5333,7 +5333,7 @@ static int init_server_components() } } - if (opt_bin_log) + if (!opt_help && opt_bin_log) { log_bin_basename= rpl_make_log_name(opt_bin_logname, pidfile_name, -- cgit v1.2.1 From 629449172a5b0a6975663ca1ac420789e00b941d Mon Sep 17 00:00:00 2001 From: Rucha Deodhar Date: Fri, 30 Apr 2021 23:14:57 +0530 Subject: MDEV-25462: Assertion `m_status == DA_ERROR || m_status == DA_OK || m_status == DA_OK_BULK' failed in Diagnostics_area::message from get_schema_tables_record Analysis: SET NAMES changes character set for character_set_client, character_set_connection, character_set_results to 'filename'. The .frm file of view has @xx sequences in the SELECT query, which give parsing error because 'filename' character set is not parser friendly. When we get parsing error (ER_PARSE_ERROR), we directly return true without setting error status. This is caught later in assertion. Fix: Disallow 'filename' character set in SET NAMES because it is not parser friendly. --- mysql-test/r/ctype_filename.result | 29 +++++++++++++++++++++++++++++ mysql-test/t/ctype_filename.test | 26 ++++++++++++++++++++++++++ sql/sql_class.h | 2 +- sql/sql_view.cc | 7 +++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/ctype_filename.result b/mysql-test/r/ctype_filename.result index c6d7d1e39b9..46b48113f9d 100644 --- a/mysql-test/r/ctype_filename.result +++ b/mysql-test/r/ctype_filename.result @@ -21,3 +21,32 @@ SET NAMES utf8; SELECT @a:=CONVERT('aя' USING filename) AS `@a`, BINARY @a, REVERSE(@a), HEX(@a), HEX(REVERSE(@a)); @a BINARY @a REVERSE(@a) HEX(@a) HEX(REVERSE(@a)) aя a@r1 яa 61407231 40723161 +# +# Beginning of 10.2 test. +# +# MDEV-25462: Assertion `m_status == DA_ERROR || m_status == DA_OK || +# m_status == DA_OK_BULK' failed in Diagnostics_area::message from +# get_schema_tables_record +# +SELECT @@character_set_client, @@character_set_connection, @@character_set_results; +@@character_set_client @@character_set_connection @@character_set_results +utf8 utf8 utf8 +SET @old_character_set_client= @@character_set_client; +SET @old_character_set_connection= @@character_set_connection; +SET @old_character_set_results= @@character_set_results; +SET NAMES 'filename'; +ERROR 42000: Variable 'character_set_client' can't be set to the value of 'filename' +SELECT @@character_set_client, @@character_set_connection, @@character_set_results; +@@character_set_client @@character_set_connection @@character_set_results +utf8 utf8 utf8 +CREATE VIEW v2 AS SELECT 1; +SHOW TABLE STATUS; +Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment +v2 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL VIEW +DROP VIEW v2; +SET @@character_set_client= @old_character_set_client; +SET @@character_set_connection= @old_character_set_connection; +SET @@character_set_results= @old_character_set_results; +# +# End of 10.2 test +# diff --git a/mysql-test/t/ctype_filename.test b/mysql-test/t/ctype_filename.test index 7ec07293a2b..99735b33ddc 100644 --- a/mysql-test/t/ctype_filename.test +++ b/mysql-test/t/ctype_filename.test @@ -27,3 +27,29 @@ select convert(convert(',' using filename) using binary); --echo # SET NAMES utf8; SELECT @a:=CONVERT('aя' USING filename) AS `@a`, BINARY @a, REVERSE(@a), HEX(@a), HEX(REVERSE(@a)); + +--echo # +--echo # Beginning of 10.2 test. +--echo # +--echo # MDEV-25462: Assertion `m_status == DA_ERROR || m_status == DA_OK || +--echo # m_status == DA_OK_BULK' failed in Diagnostics_area::message from +--echo # get_schema_tables_record +--echo # + +SELECT @@character_set_client, @@character_set_connection, @@character_set_results; +SET @old_character_set_client= @@character_set_client; +SET @old_character_set_connection= @@character_set_connection; +SET @old_character_set_results= @@character_set_results; +--error ER_WRONG_VALUE_FOR_VAR +SET NAMES 'filename'; +SELECT @@character_set_client, @@character_set_connection, @@character_set_results; +CREATE VIEW v2 AS SELECT 1; +SHOW TABLE STATUS; +DROP VIEW v2; +SET @@character_set_client= @old_character_set_client; +SET @@character_set_connection= @old_character_set_connection; +SET @@character_set_results= @old_character_set_results; + +--echo # +--echo # End of 10.2 test +--echo # diff --git a/sql/sql_class.h b/sql/sql_class.h index 890cce7bcb2..472d6294cf8 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -921,7 +921,7 @@ mysqld_collation_get_by_name(const char *name, static inline bool is_supported_parser_charset(CHARSET_INFO *cs) { - return MY_TEST(cs->mbminlen == 1); + return MY_TEST(cs->mbminlen == 1 && cs->number != 17 /* filename */); } #ifdef MYSQL_SERVER diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 0701c5233ac..0547a2fe856 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -878,6 +878,13 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, { LEX *lex= thd->lex; + /* + Ensure character set number != 17 (character set = filename) and mbminlen=1 + because these character sets are not parser friendly, which can give weird + sequence in .frm file of view and later give parsing error. + */ + DBUG_ASSERT(thd->charset()->mbminlen == 1 && thd->charset()->number != 17); + /* View definition query -- a SELECT statement that fully defines view. It is generated from the Item-tree built from the original (specified by -- cgit v1.2.1 From 8c8a6ed3b8e2bf6d9c0c155ba9a987c0ff27ac6c Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Fri, 21 May 2021 03:11:48 +0200 Subject: MDEV-25719: stunnel uses "verifyChain" without subject checks Another batch of changes that should make the SST process more reliable in all scenarios: 1) Added hostname or CN verification when stunnel is used with certificate chain verification (verifyChain = yes); 2) Added check for the absence of the stunnel utility for mtr tests; 3) Deletion of working files before and after SST is done more accurately; 4) rsync on joiner can be run even if the path to its configuration file contains spaces; 5) More accurate directory creation (for data files and for logs); 6) IST with mysqldump no longer turns off statement logging; 7) Reset password for mysqldump when password is empty but username is specified; 8) More reliable quoting when generating statements in wsrep_sst_mysqldump; 9) Added explicit generation of 2048-bit Diffie-Hellman parameters for sockat < 1.7.3, by analogy with xtrabackup; 10) Compression parameters for qpress are read from all suitable server groups in configuration file, as well as from the [sst] and [xtrabackup] groups; 11) Added a test that checks compression using qpress; 12) Checking for optional utilities is modified to work even if they implemented as built-in shell commands (unlikely on real systems, but more reliable). --- mysql-test/suite.pm | 4 +- mysql-test/suite/galera/disabled.def | 4 +- mysql-test/suite/galera/include/have_qpress.inc | 4 + mysql-test/suite/galera/include/have_stunnel.inc | 4 + .../galera/r/galera_sst_mariabackup_qpress.result | 4 + mysql-test/suite/galera/suite.pm | 9 ++ .../galera/t/galera_ist_innodb_flush_logs.cnf | 1 - mysql-test/suite/galera/t/galera_ist_rsync.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.inc | 1 - mysql-test/suite/galera/t/galera_log_bin_opt.cnf | 2 +- .../galera/t/galera_sst_mariabackup_qpress.cnf | 12 ++ .../galera/t/galera_sst_mariabackup_qpress.test | 24 ++++ .../t/galera_sst_rsync_encrypt_with_key.test | 1 + .../t/galera_sst_rsync_encrypt_with_server.test | 1 + .../suite/galera/t/galera_wan_restart_sst.test | 3 +- scripts/wsrep_sst_common.sh | 88 ++++++++++++-- scripts/wsrep_sst_mariabackup.sh | 129 +++++++++++++-------- scripts/wsrep_sst_mysqldump.sh | 64 +++++----- scripts/wsrep_sst_rsync.sh | 110 ++++++++++++------ scripts/wsrep_sst_xtrabackup-v2.sh | 121 +++++-------------- scripts/wsrep_sst_xtrabackup.sh | 12 +- 22 files changed, 367 insertions(+), 233 deletions(-) create mode 100644 mysql-test/suite/galera/include/have_qpress.inc create mode 100644 mysql-test/suite/galera/include/have_stunnel.inc create mode 100644 mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test diff --git a/mysql-test/suite.pm b/mysql-test/suite.pm index 5fc6f00b460..31e59781064 100644 --- a/mysql-test/suite.pm +++ b/mysql-test/suite.pm @@ -63,7 +63,7 @@ sub skip_combinations { unless ::have_mariabackup(); $skip{'include/have_mariabackup.inc'} = 'Need socket statistics utility' - unless IS_WINDOWS || ::which("ss"); + unless IS_WINDOWS || ! ::have_wsrep() || ::which("lsof") || ::which("sockstat") || ::which("ss"); $skip{'include/have_mariabackup.inc'} = 'Need socat or nc' unless IS_WINDOWS || $ENV{MTR_GALERA_TFMT}; @@ -95,7 +95,6 @@ sub skip_combinations { unless $::mysqld_variables{'version-ssl-library'} =~ /OpenSSL (\S+)/ and $1 ge "1.0.1d" and $1 lt "1.1.1"; - $skip{'t/ssl_7937.combinations'} = [ 'x509v3' ] unless $::mysqld_variables{'version-ssl-library'} =~ /OpenSSL (\S+)/ and $1 ge "1.0.2"; @@ -108,4 +107,3 @@ sub skip_combinations { } bless { }; - diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index 176b83d56b2..d92f3f7d6b8 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -21,10 +21,10 @@ galera_parallel_simple : MDEV-20318 galera.galera_parallel_simple fails galera_partition : MDEV-21806: galera.galera_partition MTR failed: failed to recover from DONOR state galera_shutdown_nonprim : MDEV-21493 galera.galera_shutdown_nonprim galera_var_node_address : MDEV-20485 Galera test failure -galera_wan : MDEV-17259 Test failure on galera.galera_wan +#galera_wan : MDEV-17259 Test failure on galera.galera_wan partition : MDEV-19958 Galera test failure on galera.partition query_cache: MDEV-15805 Test failure on galera.query_cache -sql_log_bin : MDEV-21491 galera.sql_log_bin +#sql_log_bin : MDEV-21491 galera.sql_log_bin versioning_trx_id: MDEV-18590: galera.versioning_trx_id: Test failure: mysqltest: Result content mismatch galera_wsrep_provider_unset_set: wsrep_provider is read-only for security reasons pxc-421: wsrep_provider is read-only for security reasons diff --git a/mysql-test/suite/galera/include/have_qpress.inc b/mysql-test/suite/galera/include/have_qpress.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_qpress.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/include/have_stunnel.inc b/mysql-test/suite/galera/include/have_stunnel.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_stunnel.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result new file mode 100644 index 00000000000..684a6d00c7e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -0,0 +1,4 @@ +SELECT 1; +1 +1 +include/assert_grep.inc [Compressed qpress files found] diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index 8ef5965834f..9ceedf82a0e 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -69,4 +69,13 @@ push @::global_suppressions, qr|WSREP: Trying to continue unpaused monitor|, ); +sub skip_combinations { + my %skip = (); + $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" + unless ::which("stunnel"); + $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" + unless ::which("qpress"); + %skip; +} + bless { }; diff --git a/mysql-test/suite/galera/t/galera_ist_innodb_flush_logs.cnf b/mysql-test/suite/galera/t/galera_ist_innodb_flush_logs.cnf index a93c291b47c..2c0cec0ab5a 100644 --- a/mysql-test/suite/galera/t/galera_ist_innodb_flush_logs.cnf +++ b/mysql-test/suite/galera/t/galera_ist_innodb_flush_logs.cnf @@ -12,4 +12,3 @@ wsrep_provider_options='base_port=@mysqld.1.#galera_port;pc.ignore_sb=true' [mysqld.2] wsrep_provider_options='base_port=@mysqld.2.#galera_port;pc.ignore_sb=true' - diff --git a/mysql-test/suite/galera/t/galera_ist_rsync.cnf b/mysql-test/suite/galera/t/galera_ist_rsync.cnf index 797e3651967..7a3da6dad02 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync.cnf +++ b/mysql-test/suite/galera/t/galera_ist_rsync.cnf @@ -10,4 +10,3 @@ wsrep_sync_wait=1 [mysqld.2] wsrep_provider_options='base_port=@mysqld.2.#galera_port;pc.ignore_sb=true' wsrep_sync_wait=1 - diff --git a/mysql-test/suite/galera/t/galera_log_bin.cnf b/mysql-test/suite/galera/t/galera_log_bin.cnf index 8f6a760def0..98e724fb2d0 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin.cnf @@ -7,4 +7,3 @@ log-slave-updates [mysqld.2] log-bin log-slave-updates - diff --git a/mysql-test/suite/galera/t/galera_log_bin.inc b/mysql-test/suite/galera/t/galera_log_bin.inc index f1d2a12b9de..12d6388615c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.inc +++ b/mysql-test/suite/galera/t/galera_log_bin.inc @@ -39,4 +39,3 @@ DROP TABLE t2; --echo #cleanup --connection node_1 RESET MASTER; - diff --git a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf index a09efd2e011..3c14cde794c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf @@ -12,4 +12,4 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port;gcache.size=1;pc.ignore [sst] transferfmt=@ENV.MTR_GALERA_TFMT -streamfmt=xbstream +streamfmt=mbstream diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf new file mode 100644 index 00000000000..25a9d5fc0b2 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +wsrep_debug=ON + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +compress=quicklz +compress-threads=2 +compress-chunk-size=32768 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test new file mode 100644 index 00000000000..b5386885ca0 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test @@ -0,0 +1,24 @@ +# +# This test checks that qpress compression works with mariabackup +# Initial SST happens via mariabackup, so there is not much to do in the body +# of the test +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source include/have_qpress.inc + +SELECT 1; + +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Confirm that transfer was compressed using qpress algorithm +--let $assert_text = Compressed qpress files found +--let $assert_select = Compressed qpress files found +--let $assert_count = 1 +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_only_after = CURRENT_TEST +--source include/assert_grep.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_wan_restart_sst.test b/mysql-test/suite/galera/t/galera_wan_restart_sst.test index 4bd7e6e71fb..16e073e7164 100644 --- a/mysql-test/suite/galera/t/galera_wan_restart_sst.test +++ b/mysql-test/suite/galera/t/galera_wan_restart_sst.test @@ -4,7 +4,7 @@ # We can not easily restart the first node, so instead we restart all the other nodes. MTR does not allow multiple nodes # to be down at the same time, so restarts are sequential. # -# We can not test any of the actual WAN optimizations from inside MTR and no +# We can not test any of the actual WAN optimizations from inside MTR and no # status variables are provided. So we only check that simple replication works. # @@ -120,7 +120,6 @@ INSERT INTO t1 VALUES (33); INSERT INTO t1 VALUES (341); - # # Check all nodes # diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index c98b388a1e2..f4ac2e9936d 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,5 +1,5 @@ -# Copyright (C) 2012-2015 Codership Oy # Copyright (C) 2017-2021 MariaDB +# Copyright (C) 2012-2015 Codership Oy # # 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 @@ -822,14 +822,15 @@ wsrep_log_info() wsrep_cleanup_progress_file() { - [ -n "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true + [ -n "$SST_PROGRESS_FILE" -a \ + -f "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true } wsrep_check_program() { local prog="$1" local cmd=$(command -v "$prog") - if [ ! -x "$cmd" ]; then + if [ -z "$cmd" ]; then echo "'$prog' not found in PATH" return 2 # no such file or directory fi @@ -865,9 +866,9 @@ get_openssl() fi # Let's look for openssl: OPENSSL_BINARY="$(command -v openssl)" - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY='/usr/bin/openssl' - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY="" fi fi @@ -899,14 +900,14 @@ is_local_ip() [ "$1" = "$(hostname -d)" ] && return 0 local ip_util="$(command -v ip)" - if [ -x "$ip_util" ]; then + if [ -n "$ip_util" ]; then # ip address show ouput format is " inet[6]
/": "$ip_util" address show \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+/" -o \ | grep -F " $1/" >/dev/null && return 0 else local ifconfig_util="$(command -v ifconfig)" - if [ -x "$ifconfig_util" ]; then + if [ -n "$ifconfig_util" ]; then # ifconfig output format is " inet[6]
...": "$ifconfig_util" \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+ " -o \ @@ -923,16 +924,79 @@ check_sockets_utils() sockstat_available=0 ss_available=0 - [ -x "$(command -v lsof)" ] && lsof_available=1 - [ -x "$(command -v sockstat)" ] && sockstat_available=1 - [ -x "$(command -v ss)" ] && ss_available=1 + [ -n "$(command -v lsof)" ] && lsof_available=1 + [ -n "$(command -v sockstat)" ] && sockstat_available=1 + [ -n "$(command -v ss)" ] && ss_available=1 if [ $lsof_available -eq 0 -a \ $sockstat_available -eq 0 -a \ $ss_available -eq 0 ] then - wsrep_log_error "Neither lsof tool, nor ss or sockstat was found in " \ - "the PATH! Make sure you have it installed." + wsrep_log_error "Neither lsof, nor sockstat or ss tool was found in " \ + "the PATH. Make sure you have it installed." exit 2 # ENOENT fi } + +# +# If the ssl_dhparams variable is already set, uses that as a source +# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in +# the datadir, and creates it there if it can't find the file. +# +check_for_dhparams() +{ + if [ -z "$ssl_dhparams" ]; then + ssl_dhparams="$DATA/dhparams.pem" + if [ ! -r "$ssl_dhparams" ]; then + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + wsrep_log_info "Could not find dhparams file, creating $ssl_dhparams" + if ! "$OPENSSL_BINARY" dhparam -out "$ssl_dhparams" 2048 >/dev/null 2>&1 + then + wsrep_log_error "******** ERROR *****************************************" + wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. *" + wsrep_log_error "********************************************************" + ssl_dhparams="" + fi + else + # Rollback: if openssl is not installed, then use + # the default parameters: + ssl_dhparams="" + fi + fi + fi +} + +# +# Compares two version strings. +# The first parameter is the version to be checked; +# The second parameter is the minimum version required; +# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise. +# +check_for_version() +{ + y1=${1#*.} + [ "$y1" = "$1" ] && y1="" + z1=${y1#*.} + [ "$z1" = "$y1" ] && z1="" + x1=${1%%.*} + y1=${y1%%.*} + z1=${z1%%.*} + [ -z "$y1" ] && y1=0 + [ -z "$z1" ] && z1=0 + y2=${2#*.} + [ "$y2" = "$2" ] && y2="" + z2=${y2#*.} + [ "$z2" = "$y2" ] && z2="" + x2=${2%%.*} + y2=${y2%%.*} + z2=${z2%%.*} + [ -z "$y2" ] && y2=0 + [ -z "$z2" ] && z2=0 + [ $x1 -lt $x2 ] && return 1 + [ $x1 -gt $x2 ] && return 0 + [ $y1 -lt $y2 ] && return 1 + [ $y1 -gt $y2 ] && return 0 + [ $z1 -lt $z2 ] && return 1 + return 0 +} diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 899f3eb4f3c..5618c704dbc 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -73,6 +73,12 @@ xtmpdir="" scomp="" sdecomp="" +ssl_dhparams="" + +compress='none' +compress_chunk="" +compress_threads="" + readonly SECRET_TAG="secret" # Required for backup locks @@ -80,25 +86,24 @@ readonly SECRET_TAG="secret" # 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts="$pvopts $pvformat" fi pcmd="pv $pvopts" declare -a RC -set +e MARIABACKUP_BIN="$(command -v mariabackup)" if [ ! -x "$MARIABACKUP_BIN" ]; then wsrep_log_error 'mariabackup binary not found in $PATH' exit 42 fi -set -e MBSTREAM_BIN=mbstream DATA="$WSREP_SST_OPT_DATA" INFO_FILE="xtrabackup_galera_info" IST_FILE="xtrabackup_ist" MAGIC_FILE="$DATA/$INFO_FILE" + INNOAPPLYLOG="$DATA/mariabackup.prepare.log" INNOMOVELOG="$DATA/mariabackup.move.log" INNOBACKUPLOG="$DATA/mariabackup.backup.log" @@ -184,7 +189,7 @@ get_keys() ecmd="$ecmd -k '$ekey'" fi elif [ "$eformat" = 'xbcrypt' ]; then - if [ ! -x "$(command -v xbcrypt)" ]; then + if [ -z "$(command -v xbcrypt)" ]; then wsrep_log_error "If encryption using the xbcrypt is enabled, " \ "then you need to install xbcrypt" exit 2 @@ -268,6 +273,22 @@ get_transfer() exit 2 fi + # Determine the socat version + SOCAT_VERSION=$(socat -V 2>&1 | grep -m1 -oe '[0-9]\.[0-9][\.0-9]*') + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" + exit 2 + fi + + if ! check_for_version "$SOCAT_VERSION" "1.7.3"; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: + check_for_dhparams + sockopt=",dhparam='$ssl_dhparams'$sockopt" + fi + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then @@ -328,7 +349,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress"; then + if [ "$compress" != 'none' ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -340,7 +361,7 @@ get_footprint() adjust_progress() { - if [ ! -x "$(command -v pv)" ]; then + if [ -z "$(command -v pv)" ]; then wsrep_log_error "pv not found in path: $PATH" wsrep_log_error "Disabling all progress/rate-limiting" pcmd="" @@ -447,6 +468,14 @@ read_cnf() if [ $ssyslog -ne -1 ]; then ssyslog=$(in_config 'mysqld_safe' 'syslog') fi + + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + compress=$(parse_cnf "$encgroups" 'compress' 'none') + if [ "$compress" != 'none' ]; then + compress_chunk=$(parse_cnf "$encgroups" 'compress-chunk-size') + compress_threads=$(parse_cnf "$encgroups" 'compress-threads') + fi + fi } get_stream() @@ -480,7 +509,7 @@ get_proc() sig_joiner_cleanup() { wsrep_log_error "Removing $MAGIC_FILE file due to signal" - rm -f "$MAGIC_FILE" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" } cleanup_joiner() @@ -540,7 +569,7 @@ cleanup_donor() fi fi - rm -f "$DATA/$IST_FILE" || true + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" if [ -n "$progress" -a -p "$progress" ]; then wsrep_log_info "Cleaning up fifo file $progress" @@ -549,13 +578,8 @@ cleanup_donor() wsrep_log_info "Cleaning up temporary directories" - if [ -n "$xtmpdir" ]; then - [ -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true - fi - - if [ -n "$itmpdir" ]; then - [ -d "$itmpdir" ] && rm -rf "$itmpdir" || true - fi + [ -n "$xtmpdir" -a -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true + [ -n "$itmpdir" -a -d "$itmpdir" ] && rm -rf "$itmpdir" || true # Final cleanup pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') @@ -682,12 +706,9 @@ recv_joiner() return fi - pushd "$dir" 1>/dev/null - set +e - local ltcmd="$tcmd" if [ $tmt -gt 0 ]; then - if [ -x "$(command -v timeout)" ]; then + if [ -n "$(command -v timeout)" ]; then if timeout --help | grep -qw -- '-k'; then ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" else @@ -696,6 +717,9 @@ recv_joiner() fi fi + pushd "$dir" 1>/dev/null + set +e + if [ $wait -ne 0 ]; then wait_for_listen "$SST_PORT" "$ADDR" "$MODULE" & fi @@ -781,7 +805,7 @@ monitor_process() wsrep_check_programs "$MARIABACKUP_BIN" -rm -f "$MAGIC_FILE" +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" if [ "$WSREP_SST_OPT_ROLE" != 'joiner' -a "$WSREP_SST_OPT_ROLE" != 'donor' ]; then wsrep_log_error "Invalid role ${WSREP_SST_OPT_ROLE}" @@ -795,13 +819,6 @@ if "$MARIABACKUP_BIN" --help 2>/dev/null | grep -qw -- '--version-check'; then disver='--no-version-check' fi -iopts="$iopts --databases-exclude='lost+found'" - -if [ ${FORCE_FTWRL:-0} -eq 1 ]; then - wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" - iopts="$iopts --no-backup-locks" -fi - # if no command line argument and INNODB_DATA_HOME_DIR environment variable # is not set, try to get it from my.cnf: if [ -z "$INNODB_DATA_HOME_DIR" ]; then @@ -810,19 +827,19 @@ fi OLD_PWD="$(pwd)" +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" if [ $ssyslog -eq 1 ]; then - if [ -x "$(command -v logger)" ]; then + if [ -n "$(command -v logger)" ]; then wsrep_log_info "Logging all stderr of SST/mariabackup to syslog" exec 2> >(logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE) @@ -850,10 +867,8 @@ if [ $sstlogarchive -eq 1 ] then ARCHIVETIMESTAMP=$(date "+%Y.%m.%d-%H.%M.%S.%N") - if [ -n "$sstlogarchivedir" ] - then - if [ ! -d "$sstlogarchivedir" ] - then + if [ -n "$sstlogarchivedir" ]; then + if [ ! -d "$sstlogarchivedir" ]; then mkdir -p "$sstlogarchivedir" fi fi @@ -927,7 +942,6 @@ then if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 if [ -z "$sst_ver" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" @@ -944,6 +958,7 @@ then itmpdir="$(mktemp -d)" wsrep_log_info "Using $itmpdir as mariabackup temporary directory" + usrst=0 if [ -n "$WSREP_SST_OPT_USER" ]; then INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 @@ -1008,6 +1023,25 @@ then tcmd="$ecmd | $tcmd" fi + iopts="$iopts --databases-exclude='lost+found'" + + if [ ${FORCE_FTWRL:-0} -eq 1 ]; then + wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" + iopts="$iopts --no-backup-locks" + fi + + # if compression is enabled for backup files, then add the + # appropriate options to the mariabackup command line: + if [ "$compress" != 'none' ]; then + iopts="$iopts --compress${compress:+=$compress}" + if [ -n "$compress_threads" ]; then + iopts="$iopts --compress-threads=$compress_threads" + fi + if [ -n "$compress_chunk" ]; then + iopts="$iopts --compress-chunk-size=$compress_chunk" + fi + fi + setup_commands set +e timeit "$stagemsg-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" @@ -1082,10 +1116,12 @@ then MODULE="xtrabackup_sst" - rm -f "$DATA/$IST_FILE" + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" # May need xtrabackup_checkpoints later on - rm -f "$DATA/xtrabackup_binary" "$DATA/xtrabackup_galera_info" "$DATA/ib_logfile0" + [ -f "$DATA/xtrabackup_binary" ] && rm -f "$DATA/xtrabackup_binary" + [ -f "$DATA/xtrabackup_galera_info" ] && rm -f "$DATA/xtrabackup_galera_info" + [ -f "$DATA/ib_logfile0" ] && rm -f "$DATA/ib_logfile0" ADDR="$WSREP_SST_OPT_ADDR" @@ -1185,8 +1221,6 @@ then wsrep_log_info "Waiting for SST streaming to complete!" monitor_process $jpid - get_proc - if [ ! -s "$DATA/xtrabackup_checkpoints" ]; then wsrep_log_error "xtrabackup_checkpoints missing, failed mariabackup/SST on donor" exit 2 @@ -1203,11 +1237,15 @@ then if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then - wsrep_log_error "qpress not found in path: $PATH" + if [ -z "$(command -v qpress)" ]; then + wsrep_log_error "qpress utility not found in the path" exit 22 fi + get_proc + + dcmd="xargs -n 2 qpress -dT$nproc" + if [ -n "$progress" ] && pv --help | grep -qw -- '--line-mode'; then count=$(find "$DATA" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -1217,9 +1255,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi # Decompress the qpress files @@ -1267,6 +1303,7 @@ then MAGIC_FILE="$TDATA/$INFO_FILE" wsrep_log_info "Moving the backup to ${TDATA}" timeit "mariabackup move stage" "$INNOMOVE" + if [ $? -eq 0 ]; then wsrep_log_info "Move successful, removing ${DATA}" rm -rf "$DATA" diff --git a/scripts/wsrep_sst_mysqldump.sh b/scripts/wsrep_sst_mysqldump.sh index e227a888baf..4aa3f8e63d8 100644 --- a/scripts/wsrep_sst_mysqldump.sh +++ b/scripts/wsrep_sst_mysqldump.sh @@ -45,7 +45,12 @@ then exit $EINVAL fi -[ -n "$WSREP_SST_OPT_USER" ] && AUTH="-u$WSREP_SST_OPT_USER" || AUTH= +AUTH="" +usrst=0 +if [ -n "$WSREP_SST_OPT_USER" ]; then + AUTH="-u$WSREP_SST_OPT_USER" + usrst=1 +fi # Refs https://github.com/codership/mysql-wsrep/issues/141 # Passing password in MYSQL_PWD environment variable is considered @@ -56,9 +61,14 @@ fi # whereas (at least on Linux) unprivileged user can't see process environment # that he does not own. So while it may be not secure in the NSA sense of the # word, it is arguably more secure than passing password on the command line. -[ -n "$WSREP_SST_OPT_PSWD" ] && export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD +fi -STOP_WSREP="SET wsrep_on=OFF;" +STOP_WSREP='SET wsrep_on=OFF;' # mysqldump cannot restore CSV tables, fix this issue CSV_TABLES_FIX=" @@ -68,13 +78,13 @@ USE mysql; SET @cond = (SELECT (SUPPORT = 'YES' or SUPPORT = 'DEFAULT') FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'csv'); -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"General log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"General log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; DROP PREPARE stmt; -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"Slow log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"Slow log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; @@ -99,15 +109,15 @@ MYSQL="$MYSQL_CLIENT $WSREP_SST_OPT_CONF "\ # Check if binary logging is enabled on the joiner node. # Note: SELECT cannot be used at this point. -LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL |\ +LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Check the joiner node's server version. -SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL |\ +SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Retrieve the donor's @@global.gtid_binlog_state. -GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL |\ +GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') RESET_MASTER="" @@ -115,7 +125,7 @@ SET_GTID_BINLOG_STATE="" SQL_LOG_BIN_OFF="" # Safety check -if [ "${SERVER_VERSION%%.*}" != '5' ] +if [ ${SERVER_VERSION%%.*} -gt 5 ] then # If binary logging is enabled on the joiner node, we need to copy donor's # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be @@ -135,29 +145,29 @@ MYSQLDUMP="$MYSQLDUMP $WSREP_SST_OPT_CONF $AUTH -S$WSREP_SST_OPT_SOCKET \ --disable-keys --extended-insert --skip-lock-tables --quick --set-charset \ --skip-comments --flush-privileges --all-databases --events" -# need to disable logging when loading the dump -# reason is that dump contains ALTER TABLE for log tables, and -# this causes an error if logging is enabled -GENERAL_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG"` -SLOW_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG"` -$MYSQL -e "$STOP_WSREP SET GLOBAL GENERAL_LOG=OFF" -$MYSQL -e "$STOP_WSREP SET GLOBAL SLOW_QUERY_LOG=OFF" - -# commands to restore log settings -RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" -RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - (echo $STOP_WSREP && echo $RESET_MASTER && \ - echo $SET_GTID_BINLOG_STATE && echo $SQL_LOG_BIN_OFF && \ - echo $STOP_WSREP && $MYSQLDUMP && echo $CSV_TABLES_FIX && \ - echo $RESTORE_GENERAL_LOG && echo $RESTORE_SLOW_QUERY_LOG && \ - echo $SET_START_POSITION && echo $SET_WSREP_GTID_DOMAIN_ID \ + # need to disable logging when loading the dump + # reason is that dump contains ALTER TABLE for log tables, and + # this causes an error if logging is enabled + GENERAL_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG") + SLOW_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG") + + LOG_OFF="SET GLOBAL GENERAL_LOG=OFF; SET GLOBAL SLOW_QUERY_LOG=OFF;" + + # commands to restore log settings + RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" + RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" + + (echo "$STOP_WSREP" && echo "$LOG_OFF" && echo "$RESET_MASTER" && \ + echo "$SET_GTID_BINLOG_STATE" && echo "$SQL_LOG_BIN_OFF" && \ + echo "$STOP_WSREP" && $MYSQLDUMP && echo "$CSV_TABLES_FIX" && \ + echo "$RESTORE_GENERAL_LOG" && echo "$RESTORE_SLOW_QUERY_LOG" && \ + echo "$SET_START_POSITION" && echo "$SET_WSREP_GTID_DOMAIN_ID" \ || echo "SST failed to complete;") | $MYSQL else wsrep_log_info "Bypassing state dump." - echo $SET_START_POSITION | $MYSQL + echo "$SET_START_POSITION" | $MYSQL fi # diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 4f39835e15d..92f77eec331 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -40,13 +40,12 @@ cleanup_joiner() [ "0" != "$RSYNC_REAL_PID" ] && \ kill $RSYNC_REAL_PID && \ sleep 0.5 && \ - kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || \ - : - rm -rf "$RSYNC_CONF" - rm -f "$STUNNEL_CONF" - rm -f "$STUNNEL_PID" - rm -rf "$MAGIC_FILE" - rm -rf "$RSYNC_PID" + kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || : + [ -f "$RSYNC_CONF" ] && rm -f "$RSYNC_CONF" + [ -f "$STUNNEL_CONF" ] && rm -f "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" wsrep_log_info "Joiner cleanup done." if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then wsrep_cleanup_progress_file @@ -125,17 +124,13 @@ check_pid_and_port() } STUNNEL_CONF="$WSREP_SST_OPT_DATA/stunnel.conf" -rm -f "$STUNNEL_CONF" STUNNEL_PID="$WSREP_SST_OPT_DATA/stunnel.pid" -rm -f "$STUNNEL_PID" MAGIC_FILE="$WSREP_SST_OPT_DATA/rsync_sst_complete" -rm -rf "$MAGIC_FILE" BINLOG_TAR_FILE="$WSREP_SST_OPT_DATA/wsrep_sst_binlog.tar" BINLOG_N_FILES=1 -rm -f "$BINLOG_TAR_FILE" || : get_binlog @@ -154,13 +149,13 @@ OLD_PWD="$(pwd)" WSREP_LOG_DIR="$INNODB_LOG_GROUP_HOME" +cd "$WSREP_SST_OPT_DATA" if [ -n "$WSREP_LOG_DIR" ]; then # handle both relative and absolute paths - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$WSREP_LOG_DIR"; cd "$WSREP_LOG_DIR"; pwd -P) -else - # default to datadir - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$WSREP_LOG_DIR" ] && mkdir -p "$WSREP_LOG_DIR" + cd "$WSREP_LOG_DIR" fi +WSREP_LOG_DIR=$(pwd -P) cd "$OLD_PWD" @@ -170,13 +165,13 @@ if [ -z "$INNODB_DATA_HOME_DIR" ]; then INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" @@ -185,13 +180,13 @@ if [ -z "$INNODB_UNDO_DIR" ]; then INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_UNDO_DIR" ]; then # handle both relative and absolute paths - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_UNDO_DIR"; cd "$INNODB_UNDO_DIR"; pwd -P) -else - # default to datadir - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" fi +INNODB_UNDO_DIR=$(pwd -P) cd "$OLD_PWD" @@ -239,7 +234,7 @@ if [ -z "$SSLMODE" ]; then # Implicit verification if CA is set and the SSL mode # is not specified by user: if [ -n "$SSTCA" ]; then - if [ -x "$(command -v stunnel)" ]; then + if [ -n "$(command -v stunnel)" ]; then SSLMODE='VERIFY_CA' fi # Require SSL by default if SSL key and cert are present: @@ -260,28 +255,36 @@ then case "$SSLMODE" in 'VERIFY_IDENTITY') VERIFY_OPT='verifyPeer = yes' + CHECK_OPT="" ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT='checkHost = localhost' + else + CHECK_OPT='checkHost = $WSREP_SST_OPT_HOST_UNESCAPED' + fi ;; *) wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" exit 22 # EINVAL esac - if [ -z "$CAFILE_OPT" ] - then - wsrep_log_error "Can't have ssl-mode=$SSLMODE without CA file" + if [ -z "$CAFILE_OPT" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" exit 22 # EINVAL fi else VERIFY_OPT="" + CHECK_OPT="" fi STUNNEL="" -if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ] && wsrep_check_programs stunnel -then - wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', SSLMODE: '$SSLMODE'" - STUNNEL="stunnel $STUNNEL_CONF" +if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then + STUNNEL_BIN="$(command -v stunnel)" + if [ -n "$STUNNEL_BIN" ]; then + wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', ssl-mode='$SSLMODE'" + STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + fi fi readonly SECRET_TAG="secret" @@ -289,7 +292,13 @@ readonly SECRET_TAG="secret" if [ "$WSREP_SST_OPT_ROLE" = 'donor' ] then -cat << EOF > "$STUNNEL_CONF" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" + + if [ -n "$STUNNEL" ] + then + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} @@ -300,7 +309,9 @@ client = yes connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 ${VERIFY_OPT} +${CHECK_OPT} EOF + fi if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then @@ -366,7 +377,7 @@ EOF # first, the normal directories, so that we can detect incompatible protocol RC=0 - eval rsync ${STUNNEL:+--rsh=\"$STUNNEL\"} \ + eval rsync ${STUNNEL:+"'--rsh=$STUNNEL'"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ $WHOLE_FILE_OPT $FILTER "'$WSREP_SST_OPT_DATA/'" \ @@ -449,7 +460,7 @@ EOF fi - echo "continue" # now server can resume updating data + echo 'continue' # now server can resume updating data echo "$STATE" > "$MAGIC_FILE" @@ -487,7 +498,10 @@ then wsrep_log_error "rsync daemon already running." exit 114 # EALREADY fi - rm -rf "$RSYNC_PID" + + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" ADDR="$WSREP_SST_OPT_ADDR" RSYNC_PORT="$WSREP_SST_OPT_PORT" @@ -541,20 +555,40 @@ EOF rsync --daemon --no-detach --port "$RSYNC_PORT" --config "$RSYNC_CONF" $RSYNC_EXTRA_ARGS & RSYNC_REAL_PID=$! else - cat << EOF > "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + # Let's check if the path to the config file contains a space? + if [ "${RSYNC_CONF#* }" = "$RSYNC_CONF" ]; then + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} foreground = yes pid = $STUNNEL_PID debug = warning -debug = 6 client = no [rsync] accept = $STUNNEL_ACCEPT exec = $(command -v rsync) execargs = rsync --server --daemon --config=$RSYNC_CONF . EOF + else + # The path contains a space, so we will run it via + # shell with "eval" command: + export RSYNC_CMD="eval $(command -v rsync) --server --daemon --config='$RSYNC_CONF' ." + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +foreground = yes +pid = $STUNNEL_PID +debug = warning +client = no +[rsync] +accept = $STUNNEL_ACCEPT +exec = $SHELL +execargs = $SHELL -c \$RSYNC_CMD +EOF + fi stunnel "$STUNNEL_CONF" & RSYNC_REAL_PID=$! RSYNC_PID="$STUNNEL_PID" @@ -655,6 +689,6 @@ else exit 22 # EINVAL fi -rm -f "$BINLOG_TAR_FILE" || : +[ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" exit 0 diff --git a/scripts/wsrep_sst_xtrabackup-v2.sh b/scripts/wsrep_sst_xtrabackup-v2.sh index 6f26cd3e287..2eef6c661a5 100644 --- a/scripts/wsrep_sst_xtrabackup-v2.sh +++ b/scripts/wsrep_sst_xtrabackup-v2.sh @@ -55,8 +55,8 @@ tcmd="" rebuild=0 rebuildcmd="" payload=0 -pvformat="-F '%N => Rate:%r Avg:%a Elapsed:%t %e Bytes: %b %p' " -pvopts="-f -i 10 -N $WSREP_SST_OPT_ROLE " +pvformat="-F '%N => Rate:%r Avg:%a Elapsed:%t %e Bytes: %b %p'" +pvopts="-f -i 10 -N $WSREP_SST_OPT_ROLE" STATDIR="" uextra=0 disver="" @@ -73,7 +73,7 @@ ssl_cert="" ssl_ca="" ssl_key="" -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts+=$pvformat fi pcmd="pv $pvopts" @@ -90,7 +90,7 @@ export PATH="/usr/sbin:/sbin:$PATH" OS="$(uname)" -if [ ! -x "$(command -v lsof)" ]; then +if [ -z "$(command -v lsof)" ]; then wsrep_log_error "lsof tool not found in PATH! Make sure you have it installed." exit 2 # ENOENT fi @@ -167,31 +167,6 @@ get_keys() stagemsg+="-XB-Encrypted" } -# -# If the ssl_dhparams variable is already set, uses that as a source -# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in the -# datadir, and creates it there if it can't find the file. -# No input parameters -# -check_for_dhparams() -{ - if [[ -z "$ssl_dhparams" ]]; then - if ! [[ -r "$DATA/dhparams.pem" ]]; then - wsrep_check_programs openssl - wsrep_log_info "Could not find dhparams file, creating $DATA/dhparams.pem" - - if ! openssl dhparam -out "$DATA/dhparams.pem" 2048 >/dev/null 2>&1 - then - wsrep_log_error "******** FATAL ERROR ********************************* " - wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. " - wsrep_log_error "****************************************************** " - exit 22 - fi - fi - ssl_dhparams="$DATA/dhparams.pem" - fi -} - # # verifies that the certificate matches the private key # doing this will save us having to wait for a timeout that would @@ -322,27 +297,25 @@ get_transfer() fi # Determine the socat version - SOCAT_VERSION=`socat -V 2>&1 | grep -oe '[0-9]\.[0-9][\.0-9]*' | head -n1` - if [[ -z "$SOCAT_VERSION" ]]; then - wsrep_log_error "******** FATAL ERROR **************** " - wsrep_log_error "* Cannot determine the socat version. " - wsrep_log_error "************************************* " + SOCAT_VERSION=$(socat -V 2>&1 | grep -m1 -oe '[0-9]\.[0-9][\.0-9]*') + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" exit 2 fi - - # socat versions < 1.7.3 will have 512-bit dhparams (too small) - # so create 2048-bit dhparams and send that as a parameter - # socat version >= 1.7.3, checks to see if the peername matches the hostname - # set commonname="" to disable the peername checks - # if ! check_for_version "$SOCAT_VERSION" "1.7.3"; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]]; then # dhparams check (will create ssl_dhparams if needed) check_for_dhparams joiner_extra=",dhparam='$ssl_dhparams'" fi - fi - if check_for_version "$SOCAT_VERSION" "1.7.3"; then + else + # socat version >= 1.7.3, checks to see if the peername matches + # the hostname, then set commonname="" to disable the peername + # checks: donor_extra=',commonname=""' fi fi @@ -420,7 +393,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c -s | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress";then + if [ $(in_config 'xtrabackup' 'compress') -eq 1 ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -432,7 +405,7 @@ get_footprint() adjust_progress() { - if [ ! -x "$(command -v pv)" ]; then + if [ -z "$(command -v pv)" ]; then wsrep_log_error "pv not found in path: $PATH" wsrep_log_error "Disabling all progress/rate-limiting" pcmd="" @@ -709,7 +682,7 @@ recv_joiner() pushd "${dir}" 1>/dev/null set +e - if [ $tmt -gt 0 -a -x "$(command -v timeout)" ]; then + if [ $tmt -gt 0 -a -n "$(command -v timeout)" ]; then if timeout --help | grep -qw -- '-k';then ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" else @@ -765,42 +738,6 @@ send_donor() done } -# Returns the version string in a standardized format -# Input "1.2.3" => echoes "010203" -# Wrongly formatted values => echoes "000000" -normalize_version() -{ - local major=0 - local minor=0 - local patch=0 - - # Only parses purely numeric version numbers, 1.2.3 - # Everything after the first three values are ignored - if [[ $1 =~ ^([0-9]+)\.([0-9]+)\.?([0-9]*)([\.0-9])*$ ]]; then - major=${BASH_REMATCH[1]} - minor=${BASH_REMATCH[2]} - patch=${BASH_REMATCH[3]} - fi - - printf %02d%02d%02d $major $minor $patch -} - -# Compares two version strings -# The first parameter is the version to be checked -# The second parameter is the minimum version required -# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise -check_for_version() -{ - local local_version_str=$(normalize_version "$1") - local required_version_str=$(normalize_version "$2") - - if [[ "$local_version_str" < "$required_version_str" ]]; then - return 1 - else - return 0 - fi -} - monitor_process() { local sst_stream_pid=$1 @@ -864,7 +801,7 @@ if [ ${FORCE_FTWRL:-0} -eq 1 ]; then fi if [[ $ssyslog -eq 1 ]];then - if [ ! -x "$(command -v logger)" ]; then + if [ -z "$(command -v logger)" ]; then wsrep_log_error "logger not in path: $PATH. Ignoring" else wsrep_log_info "Logging all stderr of SST/Innobackupex to syslog" @@ -902,13 +839,13 @@ fi OLD_PWD="$(pwd)" +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" @@ -924,7 +861,6 @@ then if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 if [ -z "$WSREP_SST_OPT_SST_VER" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" @@ -941,8 +877,9 @@ then itmpdir=$(mktemp -d) wsrep_log_info "Using $itmpdir as innobackupex temporary directory" + usrst=0 if [ -n "$WSREP_SST_OPT_USER" ]; then - INNOEXTRA+=" --user='$WSREP_SST_OPT_USER'" + INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 fi @@ -1172,11 +1109,13 @@ then if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then + if [ -z "$(command -v qpress)" ]; then wsrep_log_error "qpress not found in path: $PATH" exit 22 fi + dcmd="xargs -n 2 qpress -dT$nproc" + if [[ -n "$progress" ]] && pv --help | grep -qw '--line-mode';then count=$(find "${DATA}" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -1187,9 +1126,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi # Decompress the qpress files diff --git a/scripts/wsrep_sst_xtrabackup.sh b/scripts/wsrep_sst_xtrabackup.sh index 716865ce9c5..c6dad8d1791 100644 --- a/scripts/wsrep_sst_xtrabackup.sh +++ b/scripts/wsrep_sst_xtrabackup.sh @@ -52,7 +52,7 @@ pvformat="-F '%N => Rate:%r Avg:%a Elapsed:%t %e Bytes: %b %p' " pvopts="-f -i 10 -N $WSREP_SST_OPT_ROLE " uextra=0 -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts+=$pvformat fi pcmd="pv $pvopts" @@ -218,7 +218,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c -s | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress";then + if [ $(in_config 'xtrabackup' 'compress') -eq 1 ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -637,11 +637,13 @@ then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then + if [ -z "$(command -v qpress)" ]; then wsrep_log_error "qpress not found in path: $PATH" exit 22 fi + dcmd="xargs -n 2 qpress -dT$nproc" + if [[ -n $progress ]] && pv --help | grep -qw -- '--line-mode';then count=$(find "${DATA}" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -652,9 +654,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi wsrep_log_info "Removing existing ibdata1 file" -- cgit v1.2.1 From b01a9fd817d9d8e5941008d6981338eaa7369c83 Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Fri, 21 May 2021 03:13:37 +0200 Subject: MDEV-25719: stunnel uses "verifyChain" without subject checks Another batch of changes that should make the SST process more reliable in all scenarios: 1) Added hostname or CN verification when stunnel is used with certificate chain verification (verifyChain = yes); 2) Added check for the absence of the stunnel utility for mtr tests; 3) Deletion of working files before and after SST is done more accurately; 4) rsync on joiner can be run even if the path to its configuration file contains spaces; 5) More accurate directory creation (for data files and for logs); 6) IST with mysqldump no longer turns off statement logging; 7) Reset password for mysqldump when password is empty but username is specified; 8) More reliable quoting when generating statements in wsrep_sst_mysqldump; 9) Added explicit generation of 2048-bit Diffie-Hellman parameters for sockat < 1.7.3, by analogy with xtrabackup; 10) Compression parameters for qpress are read from all suitable server groups in configuration file, as well as from the [sst] and [xtrabackup] groups; 11) Added a test that checks compression using qpress; 12) Checking for optional utilities is modified to work even if they implemented as built-in shell commands (unlikely on real systems, but more reliable). --- mysql-test/suite.pm | 2 +- mysql-test/suite/galera/disabled.def | 4 +- mysql-test/suite/galera/include/have_qpress.inc | 4 + mysql-test/suite/galera/include/have_stunnel.inc | 4 + .../galera/r/galera_sst_mariabackup_qpress.result | 4 + mysql-test/suite/galera/suite.pm | 9 ++ mysql-test/suite/galera/t/galera_ist_rsync.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.inc | 1 - mysql-test/suite/galera/t/galera_log_bin_opt.cnf | 2 +- .../galera/t/galera_sst_mariabackup_qpress.cnf | 12 ++ .../galera/t/galera_sst_mariabackup_qpress.test | 24 ++++ .../t/galera_sst_rsync_encrypt_with_key.test | 1 + .../t/galera_sst_rsync_encrypt_with_server.test | 1 + .../suite/galera/t/galera_wan_restart_sst.test | 3 +- scripts/wsrep_sst_common.sh | 88 ++++++++++++-- scripts/wsrep_sst_mariabackup.sh | 129 +++++++++++++-------- scripts/wsrep_sst_mysqldump.sh | 64 +++++----- scripts/wsrep_sst_rsync.sh | 110 ++++++++++++------ 19 files changed, 332 insertions(+), 132 deletions(-) create mode 100644 mysql-test/suite/galera/include/have_qpress.inc create mode 100644 mysql-test/suite/galera/include/have_stunnel.inc create mode 100644 mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test diff --git a/mysql-test/suite.pm b/mysql-test/suite.pm index 70c8ef401b1..2617f95d989 100644 --- a/mysql-test/suite.pm +++ b/mysql-test/suite.pm @@ -55,7 +55,7 @@ sub skip_combinations { unless ::have_mariabackup(); $skip{'include/have_mariabackup.inc'} = 'Need socket statistics utility' - unless IS_WINDOWS || ::which("ss"); + unless IS_WINDOWS || ! ::have_wsrep() || ::which("lsof") || ::which("sockstat") || ::which("ss"); $skip{'include/have_mariabackup.inc'} = 'Need socat or nc' unless IS_WINDOWS || $ENV{MTR_GALERA_TFMT}; diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index 176b83d56b2..d92f3f7d6b8 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -21,10 +21,10 @@ galera_parallel_simple : MDEV-20318 galera.galera_parallel_simple fails galera_partition : MDEV-21806: galera.galera_partition MTR failed: failed to recover from DONOR state galera_shutdown_nonprim : MDEV-21493 galera.galera_shutdown_nonprim galera_var_node_address : MDEV-20485 Galera test failure -galera_wan : MDEV-17259 Test failure on galera.galera_wan +#galera_wan : MDEV-17259 Test failure on galera.galera_wan partition : MDEV-19958 Galera test failure on galera.partition query_cache: MDEV-15805 Test failure on galera.query_cache -sql_log_bin : MDEV-21491 galera.sql_log_bin +#sql_log_bin : MDEV-21491 galera.sql_log_bin versioning_trx_id: MDEV-18590: galera.versioning_trx_id: Test failure: mysqltest: Result content mismatch galera_wsrep_provider_unset_set: wsrep_provider is read-only for security reasons pxc-421: wsrep_provider is read-only for security reasons diff --git a/mysql-test/suite/galera/include/have_qpress.inc b/mysql-test/suite/galera/include/have_qpress.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_qpress.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/include/have_stunnel.inc b/mysql-test/suite/galera/include/have_stunnel.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_stunnel.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result new file mode 100644 index 00000000000..684a6d00c7e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -0,0 +1,4 @@ +SELECT 1; +1 +1 +include/assert_grep.inc [Compressed qpress files found] diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index 8ef5965834f..9ceedf82a0e 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -69,4 +69,13 @@ push @::global_suppressions, qr|WSREP: Trying to continue unpaused monitor|, ); +sub skip_combinations { + my %skip = (); + $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" + unless ::which("stunnel"); + $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" + unless ::which("qpress"); + %skip; +} + bless { }; diff --git a/mysql-test/suite/galera/t/galera_ist_rsync.cnf b/mysql-test/suite/galera/t/galera_ist_rsync.cnf index 797e3651967..7a3da6dad02 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync.cnf +++ b/mysql-test/suite/galera/t/galera_ist_rsync.cnf @@ -10,4 +10,3 @@ wsrep_sync_wait=1 [mysqld.2] wsrep_provider_options='base_port=@mysqld.2.#galera_port;pc.ignore_sb=true' wsrep_sync_wait=1 - diff --git a/mysql-test/suite/galera/t/galera_log_bin.cnf b/mysql-test/suite/galera/t/galera_log_bin.cnf index 8f6a760def0..98e724fb2d0 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin.cnf @@ -7,4 +7,3 @@ log-slave-updates [mysqld.2] log-bin log-slave-updates - diff --git a/mysql-test/suite/galera/t/galera_log_bin.inc b/mysql-test/suite/galera/t/galera_log_bin.inc index f1d2a12b9de..12d6388615c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.inc +++ b/mysql-test/suite/galera/t/galera_log_bin.inc @@ -39,4 +39,3 @@ DROP TABLE t2; --echo #cleanup --connection node_1 RESET MASTER; - diff --git a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf index a09efd2e011..3c14cde794c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf @@ -12,4 +12,4 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port;gcache.size=1;pc.ignore [sst] transferfmt=@ENV.MTR_GALERA_TFMT -streamfmt=xbstream +streamfmt=mbstream diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf new file mode 100644 index 00000000000..25a9d5fc0b2 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +wsrep_debug=ON + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +compress=quicklz +compress-threads=2 +compress-chunk-size=32768 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test new file mode 100644 index 00000000000..b5386885ca0 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test @@ -0,0 +1,24 @@ +# +# This test checks that qpress compression works with mariabackup +# Initial SST happens via mariabackup, so there is not much to do in the body +# of the test +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source include/have_qpress.inc + +SELECT 1; + +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Confirm that transfer was compressed using qpress algorithm +--let $assert_text = Compressed qpress files found +--let $assert_select = Compressed qpress files found +--let $assert_count = 1 +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_only_after = CURRENT_TEST +--source include/assert_grep.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_wan_restart_sst.test b/mysql-test/suite/galera/t/galera_wan_restart_sst.test index 4bd7e6e71fb..16e073e7164 100644 --- a/mysql-test/suite/galera/t/galera_wan_restart_sst.test +++ b/mysql-test/suite/galera/t/galera_wan_restart_sst.test @@ -4,7 +4,7 @@ # We can not easily restart the first node, so instead we restart all the other nodes. MTR does not allow multiple nodes # to be down at the same time, so restarts are sequential. # -# We can not test any of the actual WAN optimizations from inside MTR and no +# We can not test any of the actual WAN optimizations from inside MTR and no # status variables are provided. So we only check that simple replication works. # @@ -120,7 +120,6 @@ INSERT INTO t1 VALUES (33); INSERT INTO t1 VALUES (341); - # # Check all nodes # diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index c98b388a1e2..f4ac2e9936d 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,5 +1,5 @@ -# Copyright (C) 2012-2015 Codership Oy # Copyright (C) 2017-2021 MariaDB +# Copyright (C) 2012-2015 Codership Oy # # 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 @@ -822,14 +822,15 @@ wsrep_log_info() wsrep_cleanup_progress_file() { - [ -n "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true + [ -n "$SST_PROGRESS_FILE" -a \ + -f "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true } wsrep_check_program() { local prog="$1" local cmd=$(command -v "$prog") - if [ ! -x "$cmd" ]; then + if [ -z "$cmd" ]; then echo "'$prog' not found in PATH" return 2 # no such file or directory fi @@ -865,9 +866,9 @@ get_openssl() fi # Let's look for openssl: OPENSSL_BINARY="$(command -v openssl)" - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY='/usr/bin/openssl' - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY="" fi fi @@ -899,14 +900,14 @@ is_local_ip() [ "$1" = "$(hostname -d)" ] && return 0 local ip_util="$(command -v ip)" - if [ -x "$ip_util" ]; then + if [ -n "$ip_util" ]; then # ip address show ouput format is " inet[6]
/": "$ip_util" address show \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+/" -o \ | grep -F " $1/" >/dev/null && return 0 else local ifconfig_util="$(command -v ifconfig)" - if [ -x "$ifconfig_util" ]; then + if [ -n "$ifconfig_util" ]; then # ifconfig output format is " inet[6]
...": "$ifconfig_util" \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+ " -o \ @@ -923,16 +924,79 @@ check_sockets_utils() sockstat_available=0 ss_available=0 - [ -x "$(command -v lsof)" ] && lsof_available=1 - [ -x "$(command -v sockstat)" ] && sockstat_available=1 - [ -x "$(command -v ss)" ] && ss_available=1 + [ -n "$(command -v lsof)" ] && lsof_available=1 + [ -n "$(command -v sockstat)" ] && sockstat_available=1 + [ -n "$(command -v ss)" ] && ss_available=1 if [ $lsof_available -eq 0 -a \ $sockstat_available -eq 0 -a \ $ss_available -eq 0 ] then - wsrep_log_error "Neither lsof tool, nor ss or sockstat was found in " \ - "the PATH! Make sure you have it installed." + wsrep_log_error "Neither lsof, nor sockstat or ss tool was found in " \ + "the PATH. Make sure you have it installed." exit 2 # ENOENT fi } + +# +# If the ssl_dhparams variable is already set, uses that as a source +# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in +# the datadir, and creates it there if it can't find the file. +# +check_for_dhparams() +{ + if [ -z "$ssl_dhparams" ]; then + ssl_dhparams="$DATA/dhparams.pem" + if [ ! -r "$ssl_dhparams" ]; then + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + wsrep_log_info "Could not find dhparams file, creating $ssl_dhparams" + if ! "$OPENSSL_BINARY" dhparam -out "$ssl_dhparams" 2048 >/dev/null 2>&1 + then + wsrep_log_error "******** ERROR *****************************************" + wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. *" + wsrep_log_error "********************************************************" + ssl_dhparams="" + fi + else + # Rollback: if openssl is not installed, then use + # the default parameters: + ssl_dhparams="" + fi + fi + fi +} + +# +# Compares two version strings. +# The first parameter is the version to be checked; +# The second parameter is the minimum version required; +# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise. +# +check_for_version() +{ + y1=${1#*.} + [ "$y1" = "$1" ] && y1="" + z1=${y1#*.} + [ "$z1" = "$y1" ] && z1="" + x1=${1%%.*} + y1=${y1%%.*} + z1=${z1%%.*} + [ -z "$y1" ] && y1=0 + [ -z "$z1" ] && z1=0 + y2=${2#*.} + [ "$y2" = "$2" ] && y2="" + z2=${y2#*.} + [ "$z2" = "$y2" ] && z2="" + x2=${2%%.*} + y2=${y2%%.*} + z2=${z2%%.*} + [ -z "$y2" ] && y2=0 + [ -z "$z2" ] && z2=0 + [ $x1 -lt $x2 ] && return 1 + [ $x1 -gt $x2 ] && return 0 + [ $y1 -lt $y2 ] && return 1 + [ $y1 -gt $y2 ] && return 0 + [ $z1 -lt $z2 ] && return 1 + return 0 +} diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 899f3eb4f3c..5618c704dbc 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -73,6 +73,12 @@ xtmpdir="" scomp="" sdecomp="" +ssl_dhparams="" + +compress='none' +compress_chunk="" +compress_threads="" + readonly SECRET_TAG="secret" # Required for backup locks @@ -80,25 +86,24 @@ readonly SECRET_TAG="secret" # 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts="$pvopts $pvformat" fi pcmd="pv $pvopts" declare -a RC -set +e MARIABACKUP_BIN="$(command -v mariabackup)" if [ ! -x "$MARIABACKUP_BIN" ]; then wsrep_log_error 'mariabackup binary not found in $PATH' exit 42 fi -set -e MBSTREAM_BIN=mbstream DATA="$WSREP_SST_OPT_DATA" INFO_FILE="xtrabackup_galera_info" IST_FILE="xtrabackup_ist" MAGIC_FILE="$DATA/$INFO_FILE" + INNOAPPLYLOG="$DATA/mariabackup.prepare.log" INNOMOVELOG="$DATA/mariabackup.move.log" INNOBACKUPLOG="$DATA/mariabackup.backup.log" @@ -184,7 +189,7 @@ get_keys() ecmd="$ecmd -k '$ekey'" fi elif [ "$eformat" = 'xbcrypt' ]; then - if [ ! -x "$(command -v xbcrypt)" ]; then + if [ -z "$(command -v xbcrypt)" ]; then wsrep_log_error "If encryption using the xbcrypt is enabled, " \ "then you need to install xbcrypt" exit 2 @@ -268,6 +273,22 @@ get_transfer() exit 2 fi + # Determine the socat version + SOCAT_VERSION=$(socat -V 2>&1 | grep -m1 -oe '[0-9]\.[0-9][\.0-9]*') + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" + exit 2 + fi + + if ! check_for_version "$SOCAT_VERSION" "1.7.3"; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: + check_for_dhparams + sockopt=",dhparam='$ssl_dhparams'$sockopt" + fi + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then @@ -328,7 +349,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress"; then + if [ "$compress" != 'none' ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -340,7 +361,7 @@ get_footprint() adjust_progress() { - if [ ! -x "$(command -v pv)" ]; then + if [ -z "$(command -v pv)" ]; then wsrep_log_error "pv not found in path: $PATH" wsrep_log_error "Disabling all progress/rate-limiting" pcmd="" @@ -447,6 +468,14 @@ read_cnf() if [ $ssyslog -ne -1 ]; then ssyslog=$(in_config 'mysqld_safe' 'syslog') fi + + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + compress=$(parse_cnf "$encgroups" 'compress' 'none') + if [ "$compress" != 'none' ]; then + compress_chunk=$(parse_cnf "$encgroups" 'compress-chunk-size') + compress_threads=$(parse_cnf "$encgroups" 'compress-threads') + fi + fi } get_stream() @@ -480,7 +509,7 @@ get_proc() sig_joiner_cleanup() { wsrep_log_error "Removing $MAGIC_FILE file due to signal" - rm -f "$MAGIC_FILE" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" } cleanup_joiner() @@ -540,7 +569,7 @@ cleanup_donor() fi fi - rm -f "$DATA/$IST_FILE" || true + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" if [ -n "$progress" -a -p "$progress" ]; then wsrep_log_info "Cleaning up fifo file $progress" @@ -549,13 +578,8 @@ cleanup_donor() wsrep_log_info "Cleaning up temporary directories" - if [ -n "$xtmpdir" ]; then - [ -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true - fi - - if [ -n "$itmpdir" ]; then - [ -d "$itmpdir" ] && rm -rf "$itmpdir" || true - fi + [ -n "$xtmpdir" -a -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true + [ -n "$itmpdir" -a -d "$itmpdir" ] && rm -rf "$itmpdir" || true # Final cleanup pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') @@ -682,12 +706,9 @@ recv_joiner() return fi - pushd "$dir" 1>/dev/null - set +e - local ltcmd="$tcmd" if [ $tmt -gt 0 ]; then - if [ -x "$(command -v timeout)" ]; then + if [ -n "$(command -v timeout)" ]; then if timeout --help | grep -qw -- '-k'; then ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" else @@ -696,6 +717,9 @@ recv_joiner() fi fi + pushd "$dir" 1>/dev/null + set +e + if [ $wait -ne 0 ]; then wait_for_listen "$SST_PORT" "$ADDR" "$MODULE" & fi @@ -781,7 +805,7 @@ monitor_process() wsrep_check_programs "$MARIABACKUP_BIN" -rm -f "$MAGIC_FILE" +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" if [ "$WSREP_SST_OPT_ROLE" != 'joiner' -a "$WSREP_SST_OPT_ROLE" != 'donor' ]; then wsrep_log_error "Invalid role ${WSREP_SST_OPT_ROLE}" @@ -795,13 +819,6 @@ if "$MARIABACKUP_BIN" --help 2>/dev/null | grep -qw -- '--version-check'; then disver='--no-version-check' fi -iopts="$iopts --databases-exclude='lost+found'" - -if [ ${FORCE_FTWRL:-0} -eq 1 ]; then - wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" - iopts="$iopts --no-backup-locks" -fi - # if no command line argument and INNODB_DATA_HOME_DIR environment variable # is not set, try to get it from my.cnf: if [ -z "$INNODB_DATA_HOME_DIR" ]; then @@ -810,19 +827,19 @@ fi OLD_PWD="$(pwd)" +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" if [ $ssyslog -eq 1 ]; then - if [ -x "$(command -v logger)" ]; then + if [ -n "$(command -v logger)" ]; then wsrep_log_info "Logging all stderr of SST/mariabackup to syslog" exec 2> >(logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE) @@ -850,10 +867,8 @@ if [ $sstlogarchive -eq 1 ] then ARCHIVETIMESTAMP=$(date "+%Y.%m.%d-%H.%M.%S.%N") - if [ -n "$sstlogarchivedir" ] - then - if [ ! -d "$sstlogarchivedir" ] - then + if [ -n "$sstlogarchivedir" ]; then + if [ ! -d "$sstlogarchivedir" ]; then mkdir -p "$sstlogarchivedir" fi fi @@ -927,7 +942,6 @@ then if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 if [ -z "$sst_ver" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" @@ -944,6 +958,7 @@ then itmpdir="$(mktemp -d)" wsrep_log_info "Using $itmpdir as mariabackup temporary directory" + usrst=0 if [ -n "$WSREP_SST_OPT_USER" ]; then INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 @@ -1008,6 +1023,25 @@ then tcmd="$ecmd | $tcmd" fi + iopts="$iopts --databases-exclude='lost+found'" + + if [ ${FORCE_FTWRL:-0} -eq 1 ]; then + wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" + iopts="$iopts --no-backup-locks" + fi + + # if compression is enabled for backup files, then add the + # appropriate options to the mariabackup command line: + if [ "$compress" != 'none' ]; then + iopts="$iopts --compress${compress:+=$compress}" + if [ -n "$compress_threads" ]; then + iopts="$iopts --compress-threads=$compress_threads" + fi + if [ -n "$compress_chunk" ]; then + iopts="$iopts --compress-chunk-size=$compress_chunk" + fi + fi + setup_commands set +e timeit "$stagemsg-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" @@ -1082,10 +1116,12 @@ then MODULE="xtrabackup_sst" - rm -f "$DATA/$IST_FILE" + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" # May need xtrabackup_checkpoints later on - rm -f "$DATA/xtrabackup_binary" "$DATA/xtrabackup_galera_info" "$DATA/ib_logfile0" + [ -f "$DATA/xtrabackup_binary" ] && rm -f "$DATA/xtrabackup_binary" + [ -f "$DATA/xtrabackup_galera_info" ] && rm -f "$DATA/xtrabackup_galera_info" + [ -f "$DATA/ib_logfile0" ] && rm -f "$DATA/ib_logfile0" ADDR="$WSREP_SST_OPT_ADDR" @@ -1185,8 +1221,6 @@ then wsrep_log_info "Waiting for SST streaming to complete!" monitor_process $jpid - get_proc - if [ ! -s "$DATA/xtrabackup_checkpoints" ]; then wsrep_log_error "xtrabackup_checkpoints missing, failed mariabackup/SST on donor" exit 2 @@ -1203,11 +1237,15 @@ then if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then - wsrep_log_error "qpress not found in path: $PATH" + if [ -z "$(command -v qpress)" ]; then + wsrep_log_error "qpress utility not found in the path" exit 22 fi + get_proc + + dcmd="xargs -n 2 qpress -dT$nproc" + if [ -n "$progress" ] && pv --help | grep -qw -- '--line-mode'; then count=$(find "$DATA" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -1217,9 +1255,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi # Decompress the qpress files @@ -1267,6 +1303,7 @@ then MAGIC_FILE="$TDATA/$INFO_FILE" wsrep_log_info "Moving the backup to ${TDATA}" timeit "mariabackup move stage" "$INNOMOVE" + if [ $? -eq 0 ]; then wsrep_log_info "Move successful, removing ${DATA}" rm -rf "$DATA" diff --git a/scripts/wsrep_sst_mysqldump.sh b/scripts/wsrep_sst_mysqldump.sh index e227a888baf..4aa3f8e63d8 100644 --- a/scripts/wsrep_sst_mysqldump.sh +++ b/scripts/wsrep_sst_mysqldump.sh @@ -45,7 +45,12 @@ then exit $EINVAL fi -[ -n "$WSREP_SST_OPT_USER" ] && AUTH="-u$WSREP_SST_OPT_USER" || AUTH= +AUTH="" +usrst=0 +if [ -n "$WSREP_SST_OPT_USER" ]; then + AUTH="-u$WSREP_SST_OPT_USER" + usrst=1 +fi # Refs https://github.com/codership/mysql-wsrep/issues/141 # Passing password in MYSQL_PWD environment variable is considered @@ -56,9 +61,14 @@ fi # whereas (at least on Linux) unprivileged user can't see process environment # that he does not own. So while it may be not secure in the NSA sense of the # word, it is arguably more secure than passing password on the command line. -[ -n "$WSREP_SST_OPT_PSWD" ] && export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD +fi -STOP_WSREP="SET wsrep_on=OFF;" +STOP_WSREP='SET wsrep_on=OFF;' # mysqldump cannot restore CSV tables, fix this issue CSV_TABLES_FIX=" @@ -68,13 +78,13 @@ USE mysql; SET @cond = (SELECT (SUPPORT = 'YES' or SUPPORT = 'DEFAULT') FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'csv'); -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"General log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"General log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; DROP PREPARE stmt; -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"Slow log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"Slow log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; @@ -99,15 +109,15 @@ MYSQL="$MYSQL_CLIENT $WSREP_SST_OPT_CONF "\ # Check if binary logging is enabled on the joiner node. # Note: SELECT cannot be used at this point. -LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL |\ +LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Check the joiner node's server version. -SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL |\ +SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Retrieve the donor's @@global.gtid_binlog_state. -GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL |\ +GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') RESET_MASTER="" @@ -115,7 +125,7 @@ SET_GTID_BINLOG_STATE="" SQL_LOG_BIN_OFF="" # Safety check -if [ "${SERVER_VERSION%%.*}" != '5' ] +if [ ${SERVER_VERSION%%.*} -gt 5 ] then # If binary logging is enabled on the joiner node, we need to copy donor's # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be @@ -135,29 +145,29 @@ MYSQLDUMP="$MYSQLDUMP $WSREP_SST_OPT_CONF $AUTH -S$WSREP_SST_OPT_SOCKET \ --disable-keys --extended-insert --skip-lock-tables --quick --set-charset \ --skip-comments --flush-privileges --all-databases --events" -# need to disable logging when loading the dump -# reason is that dump contains ALTER TABLE for log tables, and -# this causes an error if logging is enabled -GENERAL_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG"` -SLOW_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG"` -$MYSQL -e "$STOP_WSREP SET GLOBAL GENERAL_LOG=OFF" -$MYSQL -e "$STOP_WSREP SET GLOBAL SLOW_QUERY_LOG=OFF" - -# commands to restore log settings -RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" -RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - (echo $STOP_WSREP && echo $RESET_MASTER && \ - echo $SET_GTID_BINLOG_STATE && echo $SQL_LOG_BIN_OFF && \ - echo $STOP_WSREP && $MYSQLDUMP && echo $CSV_TABLES_FIX && \ - echo $RESTORE_GENERAL_LOG && echo $RESTORE_SLOW_QUERY_LOG && \ - echo $SET_START_POSITION && echo $SET_WSREP_GTID_DOMAIN_ID \ + # need to disable logging when loading the dump + # reason is that dump contains ALTER TABLE for log tables, and + # this causes an error if logging is enabled + GENERAL_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG") + SLOW_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG") + + LOG_OFF="SET GLOBAL GENERAL_LOG=OFF; SET GLOBAL SLOW_QUERY_LOG=OFF;" + + # commands to restore log settings + RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" + RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" + + (echo "$STOP_WSREP" && echo "$LOG_OFF" && echo "$RESET_MASTER" && \ + echo "$SET_GTID_BINLOG_STATE" && echo "$SQL_LOG_BIN_OFF" && \ + echo "$STOP_WSREP" && $MYSQLDUMP && echo "$CSV_TABLES_FIX" && \ + echo "$RESTORE_GENERAL_LOG" && echo "$RESTORE_SLOW_QUERY_LOG" && \ + echo "$SET_START_POSITION" && echo "$SET_WSREP_GTID_DOMAIN_ID" \ || echo "SST failed to complete;") | $MYSQL else wsrep_log_info "Bypassing state dump." - echo $SET_START_POSITION | $MYSQL + echo "$SET_START_POSITION" | $MYSQL fi # diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 4f39835e15d..92f77eec331 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -40,13 +40,12 @@ cleanup_joiner() [ "0" != "$RSYNC_REAL_PID" ] && \ kill $RSYNC_REAL_PID && \ sleep 0.5 && \ - kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || \ - : - rm -rf "$RSYNC_CONF" - rm -f "$STUNNEL_CONF" - rm -f "$STUNNEL_PID" - rm -rf "$MAGIC_FILE" - rm -rf "$RSYNC_PID" + kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || : + [ -f "$RSYNC_CONF" ] && rm -f "$RSYNC_CONF" + [ -f "$STUNNEL_CONF" ] && rm -f "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" wsrep_log_info "Joiner cleanup done." if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then wsrep_cleanup_progress_file @@ -125,17 +124,13 @@ check_pid_and_port() } STUNNEL_CONF="$WSREP_SST_OPT_DATA/stunnel.conf" -rm -f "$STUNNEL_CONF" STUNNEL_PID="$WSREP_SST_OPT_DATA/stunnel.pid" -rm -f "$STUNNEL_PID" MAGIC_FILE="$WSREP_SST_OPT_DATA/rsync_sst_complete" -rm -rf "$MAGIC_FILE" BINLOG_TAR_FILE="$WSREP_SST_OPT_DATA/wsrep_sst_binlog.tar" BINLOG_N_FILES=1 -rm -f "$BINLOG_TAR_FILE" || : get_binlog @@ -154,13 +149,13 @@ OLD_PWD="$(pwd)" WSREP_LOG_DIR="$INNODB_LOG_GROUP_HOME" +cd "$WSREP_SST_OPT_DATA" if [ -n "$WSREP_LOG_DIR" ]; then # handle both relative and absolute paths - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$WSREP_LOG_DIR"; cd "$WSREP_LOG_DIR"; pwd -P) -else - # default to datadir - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$WSREP_LOG_DIR" ] && mkdir -p "$WSREP_LOG_DIR" + cd "$WSREP_LOG_DIR" fi +WSREP_LOG_DIR=$(pwd -P) cd "$OLD_PWD" @@ -170,13 +165,13 @@ if [ -z "$INNODB_DATA_HOME_DIR" ]; then INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" @@ -185,13 +180,13 @@ if [ -z "$INNODB_UNDO_DIR" ]; then INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_UNDO_DIR" ]; then # handle both relative and absolute paths - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_UNDO_DIR"; cd "$INNODB_UNDO_DIR"; pwd -P) -else - # default to datadir - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" fi +INNODB_UNDO_DIR=$(pwd -P) cd "$OLD_PWD" @@ -239,7 +234,7 @@ if [ -z "$SSLMODE" ]; then # Implicit verification if CA is set and the SSL mode # is not specified by user: if [ -n "$SSTCA" ]; then - if [ -x "$(command -v stunnel)" ]; then + if [ -n "$(command -v stunnel)" ]; then SSLMODE='VERIFY_CA' fi # Require SSL by default if SSL key and cert are present: @@ -260,28 +255,36 @@ then case "$SSLMODE" in 'VERIFY_IDENTITY') VERIFY_OPT='verifyPeer = yes' + CHECK_OPT="" ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT='checkHost = localhost' + else + CHECK_OPT='checkHost = $WSREP_SST_OPT_HOST_UNESCAPED' + fi ;; *) wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" exit 22 # EINVAL esac - if [ -z "$CAFILE_OPT" ] - then - wsrep_log_error "Can't have ssl-mode=$SSLMODE without CA file" + if [ -z "$CAFILE_OPT" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" exit 22 # EINVAL fi else VERIFY_OPT="" + CHECK_OPT="" fi STUNNEL="" -if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ] && wsrep_check_programs stunnel -then - wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', SSLMODE: '$SSLMODE'" - STUNNEL="stunnel $STUNNEL_CONF" +if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then + STUNNEL_BIN="$(command -v stunnel)" + if [ -n "$STUNNEL_BIN" ]; then + wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', ssl-mode='$SSLMODE'" + STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + fi fi readonly SECRET_TAG="secret" @@ -289,7 +292,13 @@ readonly SECRET_TAG="secret" if [ "$WSREP_SST_OPT_ROLE" = 'donor' ] then -cat << EOF > "$STUNNEL_CONF" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" + + if [ -n "$STUNNEL" ] + then + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} @@ -300,7 +309,9 @@ client = yes connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 ${VERIFY_OPT} +${CHECK_OPT} EOF + fi if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then @@ -366,7 +377,7 @@ EOF # first, the normal directories, so that we can detect incompatible protocol RC=0 - eval rsync ${STUNNEL:+--rsh=\"$STUNNEL\"} \ + eval rsync ${STUNNEL:+"'--rsh=$STUNNEL'"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ $WHOLE_FILE_OPT $FILTER "'$WSREP_SST_OPT_DATA/'" \ @@ -449,7 +460,7 @@ EOF fi - echo "continue" # now server can resume updating data + echo 'continue' # now server can resume updating data echo "$STATE" > "$MAGIC_FILE" @@ -487,7 +498,10 @@ then wsrep_log_error "rsync daemon already running." exit 114 # EALREADY fi - rm -rf "$RSYNC_PID" + + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" ADDR="$WSREP_SST_OPT_ADDR" RSYNC_PORT="$WSREP_SST_OPT_PORT" @@ -541,20 +555,40 @@ EOF rsync --daemon --no-detach --port "$RSYNC_PORT" --config "$RSYNC_CONF" $RSYNC_EXTRA_ARGS & RSYNC_REAL_PID=$! else - cat << EOF > "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + # Let's check if the path to the config file contains a space? + if [ "${RSYNC_CONF#* }" = "$RSYNC_CONF" ]; then + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} foreground = yes pid = $STUNNEL_PID debug = warning -debug = 6 client = no [rsync] accept = $STUNNEL_ACCEPT exec = $(command -v rsync) execargs = rsync --server --daemon --config=$RSYNC_CONF . EOF + else + # The path contains a space, so we will run it via + # shell with "eval" command: + export RSYNC_CMD="eval $(command -v rsync) --server --daemon --config='$RSYNC_CONF' ." + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +foreground = yes +pid = $STUNNEL_PID +debug = warning +client = no +[rsync] +accept = $STUNNEL_ACCEPT +exec = $SHELL +execargs = $SHELL -c \$RSYNC_CMD +EOF + fi stunnel "$STUNNEL_CONF" & RSYNC_REAL_PID=$! RSYNC_PID="$STUNNEL_PID" @@ -655,6 +689,6 @@ else exit 22 # EINVAL fi -rm -f "$BINLOG_TAR_FILE" || : +[ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" exit 0 -- cgit v1.2.1 From b2556b256b789aa51df1569ba7e5ce14d2e2e8ab Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Fri, 21 May 2021 03:39:58 +0200 Subject: MDEV-25719: stunnel uses "verifyChain" without subject checks Another batch of changes that should make the SST process more reliable in all scenarios: 1) Added hostname or CN verification when stunnel is used with certificate chain verification (verifyChain = yes); 2) Added check for the absence of the stunnel utility for mtr tests; 3) Deletion of working files before and after SST is done more accurately; 4) rsync on joiner can be run even if the path to its configuration file contains spaces; 5) More accurate directory creation (for data files and for logs); 6) IST with mysqldump no longer turns off statement logging; 7) Reset password for mysqldump when password is empty but username is specified; 8) More reliable quoting when generating statements in wsrep_sst_mysqldump; 9) Added explicit generation of 2048-bit Diffie-Hellman parameters for sockat < 1.7.3, by analogy with xtrabackup; 10) Compression parameters for qpress are read from all suitable server groups in configuration file, as well as from the [sst] and [xtrabackup] groups; 11) Added a test that checks compression using qpress; 12) Checking for optional utilities is modified to work even if they implemented as built-in shell commands (unlikely on real systems, but more reliable). --- mysql-test/suite/galera/disabled.def | 4 +- mysql-test/suite/galera/include/have_qpress.inc | 4 + mysql-test/suite/galera/include/have_stunnel.inc | 4 + .../galera/r/galera_sst_mariabackup_qpress.result | 4 + mysql-test/suite/galera/suite.pm | 9 +- mysql-test/suite/galera/t/galera_ist_rsync.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin_opt.cnf | 2 +- .../galera/t/galera_sst_mariabackup_qpress.cnf | 12 ++ .../galera/t/galera_sst_mariabackup_qpress.test | 24 ++++ .../t/galera_sst_rsync_encrypt_with_key.test | 1 + .../t/galera_sst_rsync_encrypt_with_server.test | 1 + .../suite/galera/t/galera_wan_restart_sst.test | 3 +- mysql-test/suite/galera_3nodes/suite.pm | 9 ++ scripts/wsrep_sst_common.sh | 88 ++++++++++++-- scripts/wsrep_sst_mariabackup.sh | 129 +++++++++++++-------- scripts/wsrep_sst_mysqldump.sh | 64 +++++----- scripts/wsrep_sst_rsync.sh | 110 ++++++++++++------ 18 files changed, 339 insertions(+), 131 deletions(-) create mode 100644 mysql-test/suite/galera/include/have_qpress.inc create mode 100644 mysql-test/suite/galera/include/have_stunnel.inc create mode 100644 mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index d39daea88aa..38ce0bd0e1d 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -42,11 +42,11 @@ galera_var_notify_cmd : MDEV-21905 Galera test galera_var_notify_cmd causes hang galera_var_reject_queries : assertion in inline_mysql_socket_send galera_var_replicate_myisam_on : MDEV-24062 Galera test failure on galera_var_replicate_myisam_on galera_var_retry_autocommit: MDEV-18181 Galera test failure on galera.galera_var_retry_autocommit -galera_wan : MDEV-17259 Test failure on galera.galera_wan +#galera_wan : MDEV-17259 Test failure on galera.galera_wan mysql-wsrep#198 : MDEV-24446: galera.mysql-wsrep#198 MTR failed: query 'reap' failed: 2000: Unknown MySQL error partition : MDEV-19958 Galera test failure on galera.partition query_cache: MDEV-15805 Test failure on galera.query_cache -sql_log_bin : MDEV-21491 galera.sql_log_bin +#sql_log_bin : MDEV-21491 galera.sql_log_bin versioning_trx_id: MDEV-18590: galera.versioning_trx_id: Test failure: mysqltest: Result content mismatch galera_wsrep_provider_unset_set: wsrep_provider is read-only for security reasons pxc-421: wsrep_provider is read-only for security reasons diff --git a/mysql-test/suite/galera/include/have_qpress.inc b/mysql-test/suite/galera/include/have_qpress.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_qpress.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/include/have_stunnel.inc b/mysql-test/suite/galera/include/have_stunnel.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_stunnel.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result new file mode 100644 index 00000000000..684a6d00c7e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -0,0 +1,4 @@ +SELECT 1; +1 +1 +include/assert_grep.inc [Compressed qpress files found] diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index c0b2da5f349..027100fdb30 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -67,9 +67,16 @@ push @::global_suppressions, qr|WSREP: Wait for gtid returned error 3 while waiting for prior transactions to commit before setting position|, ); +sub which($) { return `sh -c "command -v $_[0]"` } + sub skip_combinations { my %skip = (); - $skip{'include/have_mariabackup.inc'} = 'Need ss' unless `ss -V`; + $skip{'include/have_mariabackup.inc'} = 'Need socket statistics utility' + unless which("lsof") || which("sockstat") || which("ss"); + $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" + unless which("stunnel"); + $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" + unless which("qpress"); %skip; } diff --git a/mysql-test/suite/galera/t/galera_ist_rsync.cnf b/mysql-test/suite/galera/t/galera_ist_rsync.cnf index 797e3651967..7a3da6dad02 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync.cnf +++ b/mysql-test/suite/galera/t/galera_ist_rsync.cnf @@ -10,4 +10,3 @@ wsrep_sync_wait=1 [mysqld.2] wsrep_provider_options='base_port=@mysqld.2.#galera_port;pc.ignore_sb=true' wsrep_sync_wait=1 - diff --git a/mysql-test/suite/galera/t/galera_log_bin.cnf b/mysql-test/suite/galera/t/galera_log_bin.cnf index 8f6a760def0..98e724fb2d0 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin.cnf @@ -7,4 +7,3 @@ log-slave-updates [mysqld.2] log-bin log-slave-updates - diff --git a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf index a09efd2e011..3c14cde794c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf @@ -12,4 +12,4 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port;gcache.size=1;pc.ignore [sst] transferfmt=@ENV.MTR_GALERA_TFMT -streamfmt=xbstream +streamfmt=mbstream diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf new file mode 100644 index 00000000000..25a9d5fc0b2 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +wsrep_debug=ON + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +compress=quicklz +compress-threads=2 +compress-chunk-size=32768 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test new file mode 100644 index 00000000000..b5386885ca0 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test @@ -0,0 +1,24 @@ +# +# This test checks that qpress compression works with mariabackup +# Initial SST happens via mariabackup, so there is not much to do in the body +# of the test +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source include/have_qpress.inc + +SELECT 1; + +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Confirm that transfer was compressed using qpress algorithm +--let $assert_text = Compressed qpress files found +--let $assert_select = Compressed qpress files found +--let $assert_count = 1 +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_only_after = CURRENT_TEST +--source include/assert_grep.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_wan_restart_sst.test b/mysql-test/suite/galera/t/galera_wan_restart_sst.test index 24620458570..0ded4032103 100644 --- a/mysql-test/suite/galera/t/galera_wan_restart_sst.test +++ b/mysql-test/suite/galera/t/galera_wan_restart_sst.test @@ -4,7 +4,7 @@ # We can not easily restart the first node, so instead we restart all the other nodes. MTR does not allow multiple nodes # to be down at the same time, so restarts are sequential. # -# We can not test any of the actual WAN optimizations from inside MTR and no +# We can not test any of the actual WAN optimizations from inside MTR and no # status variables are provided. So we only check that simple replication works. # @@ -98,7 +98,6 @@ INSERT INTO t1 VALUES (33); INSERT INTO t1 VALUES (341); - # # Check all nodes # diff --git a/mysql-test/suite/galera_3nodes/suite.pm b/mysql-test/suite/galera_3nodes/suite.pm index f93cd0e0909..49b93248a58 100644 --- a/mysql-test/suite/galera_3nodes/suite.pm +++ b/mysql-test/suite/galera_3nodes/suite.pm @@ -67,4 +67,13 @@ push @::global_suppressions, qr|WSREP: Wait for gtid returned error 3 while waiting for prior transactions to commit before setting position|, ); +sub which($) { return `sh -c "command -v $_[0]"` } + +sub skip_combinations { + my %skip = (); + $skip{'include/have_mariabackup.inc'} = 'Need ss' + unless which("lsof") || which("sockstat") || which("ss"); + %skip; +} + bless { }; diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index c98b388a1e2..f4ac2e9936d 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,5 +1,5 @@ -# Copyright (C) 2012-2015 Codership Oy # Copyright (C) 2017-2021 MariaDB +# Copyright (C) 2012-2015 Codership Oy # # 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 @@ -822,14 +822,15 @@ wsrep_log_info() wsrep_cleanup_progress_file() { - [ -n "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true + [ -n "$SST_PROGRESS_FILE" -a \ + -f "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true } wsrep_check_program() { local prog="$1" local cmd=$(command -v "$prog") - if [ ! -x "$cmd" ]; then + if [ -z "$cmd" ]; then echo "'$prog' not found in PATH" return 2 # no such file or directory fi @@ -865,9 +866,9 @@ get_openssl() fi # Let's look for openssl: OPENSSL_BINARY="$(command -v openssl)" - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY='/usr/bin/openssl' - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY="" fi fi @@ -899,14 +900,14 @@ is_local_ip() [ "$1" = "$(hostname -d)" ] && return 0 local ip_util="$(command -v ip)" - if [ -x "$ip_util" ]; then + if [ -n "$ip_util" ]; then # ip address show ouput format is " inet[6]
/": "$ip_util" address show \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+/" -o \ | grep -F " $1/" >/dev/null && return 0 else local ifconfig_util="$(command -v ifconfig)" - if [ -x "$ifconfig_util" ]; then + if [ -n "$ifconfig_util" ]; then # ifconfig output format is " inet[6]
...": "$ifconfig_util" \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+ " -o \ @@ -923,16 +924,79 @@ check_sockets_utils() sockstat_available=0 ss_available=0 - [ -x "$(command -v lsof)" ] && lsof_available=1 - [ -x "$(command -v sockstat)" ] && sockstat_available=1 - [ -x "$(command -v ss)" ] && ss_available=1 + [ -n "$(command -v lsof)" ] && lsof_available=1 + [ -n "$(command -v sockstat)" ] && sockstat_available=1 + [ -n "$(command -v ss)" ] && ss_available=1 if [ $lsof_available -eq 0 -a \ $sockstat_available -eq 0 -a \ $ss_available -eq 0 ] then - wsrep_log_error "Neither lsof tool, nor ss or sockstat was found in " \ - "the PATH! Make sure you have it installed." + wsrep_log_error "Neither lsof, nor sockstat or ss tool was found in " \ + "the PATH. Make sure you have it installed." exit 2 # ENOENT fi } + +# +# If the ssl_dhparams variable is already set, uses that as a source +# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in +# the datadir, and creates it there if it can't find the file. +# +check_for_dhparams() +{ + if [ -z "$ssl_dhparams" ]; then + ssl_dhparams="$DATA/dhparams.pem" + if [ ! -r "$ssl_dhparams" ]; then + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + wsrep_log_info "Could not find dhparams file, creating $ssl_dhparams" + if ! "$OPENSSL_BINARY" dhparam -out "$ssl_dhparams" 2048 >/dev/null 2>&1 + then + wsrep_log_error "******** ERROR *****************************************" + wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. *" + wsrep_log_error "********************************************************" + ssl_dhparams="" + fi + else + # Rollback: if openssl is not installed, then use + # the default parameters: + ssl_dhparams="" + fi + fi + fi +} + +# +# Compares two version strings. +# The first parameter is the version to be checked; +# The second parameter is the minimum version required; +# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise. +# +check_for_version() +{ + y1=${1#*.} + [ "$y1" = "$1" ] && y1="" + z1=${y1#*.} + [ "$z1" = "$y1" ] && z1="" + x1=${1%%.*} + y1=${y1%%.*} + z1=${z1%%.*} + [ -z "$y1" ] && y1=0 + [ -z "$z1" ] && z1=0 + y2=${2#*.} + [ "$y2" = "$2" ] && y2="" + z2=${y2#*.} + [ "$z2" = "$y2" ] && z2="" + x2=${2%%.*} + y2=${y2%%.*} + z2=${z2%%.*} + [ -z "$y2" ] && y2=0 + [ -z "$z2" ] && z2=0 + [ $x1 -lt $x2 ] && return 1 + [ $x1 -gt $x2 ] && return 0 + [ $y1 -lt $y2 ] && return 1 + [ $y1 -gt $y2 ] && return 0 + [ $z1 -lt $z2 ] && return 1 + return 0 +} diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 899f3eb4f3c..5618c704dbc 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -73,6 +73,12 @@ xtmpdir="" scomp="" sdecomp="" +ssl_dhparams="" + +compress='none' +compress_chunk="" +compress_threads="" + readonly SECRET_TAG="secret" # Required for backup locks @@ -80,25 +86,24 @@ readonly SECRET_TAG="secret" # 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts="$pvopts $pvformat" fi pcmd="pv $pvopts" declare -a RC -set +e MARIABACKUP_BIN="$(command -v mariabackup)" if [ ! -x "$MARIABACKUP_BIN" ]; then wsrep_log_error 'mariabackup binary not found in $PATH' exit 42 fi -set -e MBSTREAM_BIN=mbstream DATA="$WSREP_SST_OPT_DATA" INFO_FILE="xtrabackup_galera_info" IST_FILE="xtrabackup_ist" MAGIC_FILE="$DATA/$INFO_FILE" + INNOAPPLYLOG="$DATA/mariabackup.prepare.log" INNOMOVELOG="$DATA/mariabackup.move.log" INNOBACKUPLOG="$DATA/mariabackup.backup.log" @@ -184,7 +189,7 @@ get_keys() ecmd="$ecmd -k '$ekey'" fi elif [ "$eformat" = 'xbcrypt' ]; then - if [ ! -x "$(command -v xbcrypt)" ]; then + if [ -z "$(command -v xbcrypt)" ]; then wsrep_log_error "If encryption using the xbcrypt is enabled, " \ "then you need to install xbcrypt" exit 2 @@ -268,6 +273,22 @@ get_transfer() exit 2 fi + # Determine the socat version + SOCAT_VERSION=$(socat -V 2>&1 | grep -m1 -oe '[0-9]\.[0-9][\.0-9]*') + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" + exit 2 + fi + + if ! check_for_version "$SOCAT_VERSION" "1.7.3"; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: + check_for_dhparams + sockopt=",dhparam='$ssl_dhparams'$sockopt" + fi + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then @@ -328,7 +349,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress"; then + if [ "$compress" != 'none' ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -340,7 +361,7 @@ get_footprint() adjust_progress() { - if [ ! -x "$(command -v pv)" ]; then + if [ -z "$(command -v pv)" ]; then wsrep_log_error "pv not found in path: $PATH" wsrep_log_error "Disabling all progress/rate-limiting" pcmd="" @@ -447,6 +468,14 @@ read_cnf() if [ $ssyslog -ne -1 ]; then ssyslog=$(in_config 'mysqld_safe' 'syslog') fi + + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + compress=$(parse_cnf "$encgroups" 'compress' 'none') + if [ "$compress" != 'none' ]; then + compress_chunk=$(parse_cnf "$encgroups" 'compress-chunk-size') + compress_threads=$(parse_cnf "$encgroups" 'compress-threads') + fi + fi } get_stream() @@ -480,7 +509,7 @@ get_proc() sig_joiner_cleanup() { wsrep_log_error "Removing $MAGIC_FILE file due to signal" - rm -f "$MAGIC_FILE" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" } cleanup_joiner() @@ -540,7 +569,7 @@ cleanup_donor() fi fi - rm -f "$DATA/$IST_FILE" || true + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" if [ -n "$progress" -a -p "$progress" ]; then wsrep_log_info "Cleaning up fifo file $progress" @@ -549,13 +578,8 @@ cleanup_donor() wsrep_log_info "Cleaning up temporary directories" - if [ -n "$xtmpdir" ]; then - [ -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true - fi - - if [ -n "$itmpdir" ]; then - [ -d "$itmpdir" ] && rm -rf "$itmpdir" || true - fi + [ -n "$xtmpdir" -a -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true + [ -n "$itmpdir" -a -d "$itmpdir" ] && rm -rf "$itmpdir" || true # Final cleanup pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') @@ -682,12 +706,9 @@ recv_joiner() return fi - pushd "$dir" 1>/dev/null - set +e - local ltcmd="$tcmd" if [ $tmt -gt 0 ]; then - if [ -x "$(command -v timeout)" ]; then + if [ -n "$(command -v timeout)" ]; then if timeout --help | grep -qw -- '-k'; then ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" else @@ -696,6 +717,9 @@ recv_joiner() fi fi + pushd "$dir" 1>/dev/null + set +e + if [ $wait -ne 0 ]; then wait_for_listen "$SST_PORT" "$ADDR" "$MODULE" & fi @@ -781,7 +805,7 @@ monitor_process() wsrep_check_programs "$MARIABACKUP_BIN" -rm -f "$MAGIC_FILE" +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" if [ "$WSREP_SST_OPT_ROLE" != 'joiner' -a "$WSREP_SST_OPT_ROLE" != 'donor' ]; then wsrep_log_error "Invalid role ${WSREP_SST_OPT_ROLE}" @@ -795,13 +819,6 @@ if "$MARIABACKUP_BIN" --help 2>/dev/null | grep -qw -- '--version-check'; then disver='--no-version-check' fi -iopts="$iopts --databases-exclude='lost+found'" - -if [ ${FORCE_FTWRL:-0} -eq 1 ]; then - wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" - iopts="$iopts --no-backup-locks" -fi - # if no command line argument and INNODB_DATA_HOME_DIR environment variable # is not set, try to get it from my.cnf: if [ -z "$INNODB_DATA_HOME_DIR" ]; then @@ -810,19 +827,19 @@ fi OLD_PWD="$(pwd)" +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" if [ $ssyslog -eq 1 ]; then - if [ -x "$(command -v logger)" ]; then + if [ -n "$(command -v logger)" ]; then wsrep_log_info "Logging all stderr of SST/mariabackup to syslog" exec 2> >(logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE) @@ -850,10 +867,8 @@ if [ $sstlogarchive -eq 1 ] then ARCHIVETIMESTAMP=$(date "+%Y.%m.%d-%H.%M.%S.%N") - if [ -n "$sstlogarchivedir" ] - then - if [ ! -d "$sstlogarchivedir" ] - then + if [ -n "$sstlogarchivedir" ]; then + if [ ! -d "$sstlogarchivedir" ]; then mkdir -p "$sstlogarchivedir" fi fi @@ -927,7 +942,6 @@ then if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 if [ -z "$sst_ver" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" @@ -944,6 +958,7 @@ then itmpdir="$(mktemp -d)" wsrep_log_info "Using $itmpdir as mariabackup temporary directory" + usrst=0 if [ -n "$WSREP_SST_OPT_USER" ]; then INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 @@ -1008,6 +1023,25 @@ then tcmd="$ecmd | $tcmd" fi + iopts="$iopts --databases-exclude='lost+found'" + + if [ ${FORCE_FTWRL:-0} -eq 1 ]; then + wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" + iopts="$iopts --no-backup-locks" + fi + + # if compression is enabled for backup files, then add the + # appropriate options to the mariabackup command line: + if [ "$compress" != 'none' ]; then + iopts="$iopts --compress${compress:+=$compress}" + if [ -n "$compress_threads" ]; then + iopts="$iopts --compress-threads=$compress_threads" + fi + if [ -n "$compress_chunk" ]; then + iopts="$iopts --compress-chunk-size=$compress_chunk" + fi + fi + setup_commands set +e timeit "$stagemsg-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" @@ -1082,10 +1116,12 @@ then MODULE="xtrabackup_sst" - rm -f "$DATA/$IST_FILE" + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" # May need xtrabackup_checkpoints later on - rm -f "$DATA/xtrabackup_binary" "$DATA/xtrabackup_galera_info" "$DATA/ib_logfile0" + [ -f "$DATA/xtrabackup_binary" ] && rm -f "$DATA/xtrabackup_binary" + [ -f "$DATA/xtrabackup_galera_info" ] && rm -f "$DATA/xtrabackup_galera_info" + [ -f "$DATA/ib_logfile0" ] && rm -f "$DATA/ib_logfile0" ADDR="$WSREP_SST_OPT_ADDR" @@ -1185,8 +1221,6 @@ then wsrep_log_info "Waiting for SST streaming to complete!" monitor_process $jpid - get_proc - if [ ! -s "$DATA/xtrabackup_checkpoints" ]; then wsrep_log_error "xtrabackup_checkpoints missing, failed mariabackup/SST on donor" exit 2 @@ -1203,11 +1237,15 @@ then if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then - wsrep_log_error "qpress not found in path: $PATH" + if [ -z "$(command -v qpress)" ]; then + wsrep_log_error "qpress utility not found in the path" exit 22 fi + get_proc + + dcmd="xargs -n 2 qpress -dT$nproc" + if [ -n "$progress" ] && pv --help | grep -qw -- '--line-mode'; then count=$(find "$DATA" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -1217,9 +1255,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi # Decompress the qpress files @@ -1267,6 +1303,7 @@ then MAGIC_FILE="$TDATA/$INFO_FILE" wsrep_log_info "Moving the backup to ${TDATA}" timeit "mariabackup move stage" "$INNOMOVE" + if [ $? -eq 0 ]; then wsrep_log_info "Move successful, removing ${DATA}" rm -rf "$DATA" diff --git a/scripts/wsrep_sst_mysqldump.sh b/scripts/wsrep_sst_mysqldump.sh index e227a888baf..4aa3f8e63d8 100644 --- a/scripts/wsrep_sst_mysqldump.sh +++ b/scripts/wsrep_sst_mysqldump.sh @@ -45,7 +45,12 @@ then exit $EINVAL fi -[ -n "$WSREP_SST_OPT_USER" ] && AUTH="-u$WSREP_SST_OPT_USER" || AUTH= +AUTH="" +usrst=0 +if [ -n "$WSREP_SST_OPT_USER" ]; then + AUTH="-u$WSREP_SST_OPT_USER" + usrst=1 +fi # Refs https://github.com/codership/mysql-wsrep/issues/141 # Passing password in MYSQL_PWD environment variable is considered @@ -56,9 +61,14 @@ fi # whereas (at least on Linux) unprivileged user can't see process environment # that he does not own. So while it may be not secure in the NSA sense of the # word, it is arguably more secure than passing password on the command line. -[ -n "$WSREP_SST_OPT_PSWD" ] && export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD +fi -STOP_WSREP="SET wsrep_on=OFF;" +STOP_WSREP='SET wsrep_on=OFF;' # mysqldump cannot restore CSV tables, fix this issue CSV_TABLES_FIX=" @@ -68,13 +78,13 @@ USE mysql; SET @cond = (SELECT (SUPPORT = 'YES' or SUPPORT = 'DEFAULT') FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'csv'); -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"General log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"General log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; DROP PREPARE stmt; -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"Slow log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"Slow log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; @@ -99,15 +109,15 @@ MYSQL="$MYSQL_CLIENT $WSREP_SST_OPT_CONF "\ # Check if binary logging is enabled on the joiner node. # Note: SELECT cannot be used at this point. -LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL |\ +LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Check the joiner node's server version. -SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL |\ +SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Retrieve the donor's @@global.gtid_binlog_state. -GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL |\ +GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') RESET_MASTER="" @@ -115,7 +125,7 @@ SET_GTID_BINLOG_STATE="" SQL_LOG_BIN_OFF="" # Safety check -if [ "${SERVER_VERSION%%.*}" != '5' ] +if [ ${SERVER_VERSION%%.*} -gt 5 ] then # If binary logging is enabled on the joiner node, we need to copy donor's # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be @@ -135,29 +145,29 @@ MYSQLDUMP="$MYSQLDUMP $WSREP_SST_OPT_CONF $AUTH -S$WSREP_SST_OPT_SOCKET \ --disable-keys --extended-insert --skip-lock-tables --quick --set-charset \ --skip-comments --flush-privileges --all-databases --events" -# need to disable logging when loading the dump -# reason is that dump contains ALTER TABLE for log tables, and -# this causes an error if logging is enabled -GENERAL_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG"` -SLOW_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG"` -$MYSQL -e "$STOP_WSREP SET GLOBAL GENERAL_LOG=OFF" -$MYSQL -e "$STOP_WSREP SET GLOBAL SLOW_QUERY_LOG=OFF" - -# commands to restore log settings -RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" -RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - (echo $STOP_WSREP && echo $RESET_MASTER && \ - echo $SET_GTID_BINLOG_STATE && echo $SQL_LOG_BIN_OFF && \ - echo $STOP_WSREP && $MYSQLDUMP && echo $CSV_TABLES_FIX && \ - echo $RESTORE_GENERAL_LOG && echo $RESTORE_SLOW_QUERY_LOG && \ - echo $SET_START_POSITION && echo $SET_WSREP_GTID_DOMAIN_ID \ + # need to disable logging when loading the dump + # reason is that dump contains ALTER TABLE for log tables, and + # this causes an error if logging is enabled + GENERAL_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG") + SLOW_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG") + + LOG_OFF="SET GLOBAL GENERAL_LOG=OFF; SET GLOBAL SLOW_QUERY_LOG=OFF;" + + # commands to restore log settings + RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" + RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" + + (echo "$STOP_WSREP" && echo "$LOG_OFF" && echo "$RESET_MASTER" && \ + echo "$SET_GTID_BINLOG_STATE" && echo "$SQL_LOG_BIN_OFF" && \ + echo "$STOP_WSREP" && $MYSQLDUMP && echo "$CSV_TABLES_FIX" && \ + echo "$RESTORE_GENERAL_LOG" && echo "$RESTORE_SLOW_QUERY_LOG" && \ + echo "$SET_START_POSITION" && echo "$SET_WSREP_GTID_DOMAIN_ID" \ || echo "SST failed to complete;") | $MYSQL else wsrep_log_info "Bypassing state dump." - echo $SET_START_POSITION | $MYSQL + echo "$SET_START_POSITION" | $MYSQL fi # diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 4f39835e15d..92f77eec331 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -40,13 +40,12 @@ cleanup_joiner() [ "0" != "$RSYNC_REAL_PID" ] && \ kill $RSYNC_REAL_PID && \ sleep 0.5 && \ - kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || \ - : - rm -rf "$RSYNC_CONF" - rm -f "$STUNNEL_CONF" - rm -f "$STUNNEL_PID" - rm -rf "$MAGIC_FILE" - rm -rf "$RSYNC_PID" + kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || : + [ -f "$RSYNC_CONF" ] && rm -f "$RSYNC_CONF" + [ -f "$STUNNEL_CONF" ] && rm -f "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" wsrep_log_info "Joiner cleanup done." if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then wsrep_cleanup_progress_file @@ -125,17 +124,13 @@ check_pid_and_port() } STUNNEL_CONF="$WSREP_SST_OPT_DATA/stunnel.conf" -rm -f "$STUNNEL_CONF" STUNNEL_PID="$WSREP_SST_OPT_DATA/stunnel.pid" -rm -f "$STUNNEL_PID" MAGIC_FILE="$WSREP_SST_OPT_DATA/rsync_sst_complete" -rm -rf "$MAGIC_FILE" BINLOG_TAR_FILE="$WSREP_SST_OPT_DATA/wsrep_sst_binlog.tar" BINLOG_N_FILES=1 -rm -f "$BINLOG_TAR_FILE" || : get_binlog @@ -154,13 +149,13 @@ OLD_PWD="$(pwd)" WSREP_LOG_DIR="$INNODB_LOG_GROUP_HOME" +cd "$WSREP_SST_OPT_DATA" if [ -n "$WSREP_LOG_DIR" ]; then # handle both relative and absolute paths - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$WSREP_LOG_DIR"; cd "$WSREP_LOG_DIR"; pwd -P) -else - # default to datadir - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$WSREP_LOG_DIR" ] && mkdir -p "$WSREP_LOG_DIR" + cd "$WSREP_LOG_DIR" fi +WSREP_LOG_DIR=$(pwd -P) cd "$OLD_PWD" @@ -170,13 +165,13 @@ if [ -z "$INNODB_DATA_HOME_DIR" ]; then INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" @@ -185,13 +180,13 @@ if [ -z "$INNODB_UNDO_DIR" ]; then INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_UNDO_DIR" ]; then # handle both relative and absolute paths - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_UNDO_DIR"; cd "$INNODB_UNDO_DIR"; pwd -P) -else - # default to datadir - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" fi +INNODB_UNDO_DIR=$(pwd -P) cd "$OLD_PWD" @@ -239,7 +234,7 @@ if [ -z "$SSLMODE" ]; then # Implicit verification if CA is set and the SSL mode # is not specified by user: if [ -n "$SSTCA" ]; then - if [ -x "$(command -v stunnel)" ]; then + if [ -n "$(command -v stunnel)" ]; then SSLMODE='VERIFY_CA' fi # Require SSL by default if SSL key and cert are present: @@ -260,28 +255,36 @@ then case "$SSLMODE" in 'VERIFY_IDENTITY') VERIFY_OPT='verifyPeer = yes' + CHECK_OPT="" ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT='checkHost = localhost' + else + CHECK_OPT='checkHost = $WSREP_SST_OPT_HOST_UNESCAPED' + fi ;; *) wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" exit 22 # EINVAL esac - if [ -z "$CAFILE_OPT" ] - then - wsrep_log_error "Can't have ssl-mode=$SSLMODE without CA file" + if [ -z "$CAFILE_OPT" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" exit 22 # EINVAL fi else VERIFY_OPT="" + CHECK_OPT="" fi STUNNEL="" -if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ] && wsrep_check_programs stunnel -then - wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', SSLMODE: '$SSLMODE'" - STUNNEL="stunnel $STUNNEL_CONF" +if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then + STUNNEL_BIN="$(command -v stunnel)" + if [ -n "$STUNNEL_BIN" ]; then + wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', ssl-mode='$SSLMODE'" + STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + fi fi readonly SECRET_TAG="secret" @@ -289,7 +292,13 @@ readonly SECRET_TAG="secret" if [ "$WSREP_SST_OPT_ROLE" = 'donor' ] then -cat << EOF > "$STUNNEL_CONF" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" + + if [ -n "$STUNNEL" ] + then + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} @@ -300,7 +309,9 @@ client = yes connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 ${VERIFY_OPT} +${CHECK_OPT} EOF + fi if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then @@ -366,7 +377,7 @@ EOF # first, the normal directories, so that we can detect incompatible protocol RC=0 - eval rsync ${STUNNEL:+--rsh=\"$STUNNEL\"} \ + eval rsync ${STUNNEL:+"'--rsh=$STUNNEL'"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ $WHOLE_FILE_OPT $FILTER "'$WSREP_SST_OPT_DATA/'" \ @@ -449,7 +460,7 @@ EOF fi - echo "continue" # now server can resume updating data + echo 'continue' # now server can resume updating data echo "$STATE" > "$MAGIC_FILE" @@ -487,7 +498,10 @@ then wsrep_log_error "rsync daemon already running." exit 114 # EALREADY fi - rm -rf "$RSYNC_PID" + + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" ADDR="$WSREP_SST_OPT_ADDR" RSYNC_PORT="$WSREP_SST_OPT_PORT" @@ -541,20 +555,40 @@ EOF rsync --daemon --no-detach --port "$RSYNC_PORT" --config "$RSYNC_CONF" $RSYNC_EXTRA_ARGS & RSYNC_REAL_PID=$! else - cat << EOF > "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + # Let's check if the path to the config file contains a space? + if [ "${RSYNC_CONF#* }" = "$RSYNC_CONF" ]; then + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} foreground = yes pid = $STUNNEL_PID debug = warning -debug = 6 client = no [rsync] accept = $STUNNEL_ACCEPT exec = $(command -v rsync) execargs = rsync --server --daemon --config=$RSYNC_CONF . EOF + else + # The path contains a space, so we will run it via + # shell with "eval" command: + export RSYNC_CMD="eval $(command -v rsync) --server --daemon --config='$RSYNC_CONF' ." + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +foreground = yes +pid = $STUNNEL_PID +debug = warning +client = no +[rsync] +accept = $STUNNEL_ACCEPT +exec = $SHELL +execargs = $SHELL -c \$RSYNC_CMD +EOF + fi stunnel "$STUNNEL_CONF" & RSYNC_REAL_PID=$! RSYNC_PID="$STUNNEL_PID" @@ -655,6 +689,6 @@ else exit 22 # EINVAL fi -rm -f "$BINLOG_TAR_FILE" || : +[ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" exit 0 -- cgit v1.2.1 From 5667baad5d7a597d56786e6872da79696e511149 Mon Sep 17 00:00:00 2001 From: mkaruza Date: Thu, 20 May 2021 09:56:53 +0200 Subject: MDEV-25562 Assertion `pause_seqno_.is_undefined() == false' failed in void wsrep::server_state::resume() If pause() is not executed in galera and returns seqno = -1 we should skip resume(). --- mysql-test/suite/galera/r/MDEV-25562.result | 16 ++++++++++++++++ mysql-test/suite/galera/t/MDEV-25562.test | 23 +++++++++++++++++++++++ sql/lock.cc | 7 ++++--- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 mysql-test/suite/galera/r/MDEV-25562.result create mode 100644 mysql-test/suite/galera/t/MDEV-25562.test diff --git a/mysql-test/suite/galera/r/MDEV-25562.result b/mysql-test/suite/galera/r/MDEV-25562.result new file mode 100644 index 00000000000..b0d77af374b --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-25562.result @@ -0,0 +1,16 @@ +connection node_2; +connection node_1; +SET SESSION WSREP_ON=0; +FLUSH TABLES WITH READ LOCK AND DISABLE CHECKPOINT; +SET SESSION WSREP_ON=1; +UNLOCK TABLES; +SET GLOBAL wsrep_ignore_apply_errors=1; +CREATE TABLE t1 (a CHAR(1)) engine=innodb; +CREATE TABLE t1 (a CHAR(1)) engine=innodb; +ERROR 42S01: Table 't1' already exists +SHOW PROCEDURE STATUS WHERE db = 'test'; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +SET GLOBAL read_only=1; +SET GLOBAL wsrep_ignore_apply_errors=DEFAULT; +SET GLOBAL read_only=DEFAULT; +DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/MDEV-25562.test b/mysql-test/suite/galera/t/MDEV-25562.test new file mode 100644 index 00000000000..01729936b08 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-25562.test @@ -0,0 +1,23 @@ +# +# MDEV-25562 Assertion `pause_seqno_.is_undefined() == false' failed in void wsrep::server_state::resume() +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc + +SET SESSION WSREP_ON=0; +FLUSH TABLES WITH READ LOCK AND DISABLE CHECKPOINT; +SET SESSION WSREP_ON=1; +UNLOCK TABLES; + +SET GLOBAL wsrep_ignore_apply_errors=1; +CREATE TABLE t1 (a CHAR(1)) engine=innodb; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1 (a CHAR(1)) engine=innodb; +SHOW PROCEDURE STATUS WHERE db = 'test'; +SET GLOBAL read_only=1; + +SET GLOBAL wsrep_ignore_apply_errors=DEFAULT; +SET GLOBAL read_only=DEFAULT; +DROP TABLE t1; + diff --git a/sql/lock.cc b/sql/lock.cc index 3a2001fbc34..74cfa5d7c97 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -1102,21 +1102,22 @@ void Global_read_lock::unlock_global_read_lock(THD *thd) thd->mdl_context.release_lock(m_mdl_global_read_lock); #ifdef WITH_WSREP - if (m_state == GRL_ACQUIRED_AND_BLOCKS_COMMIT) + if (m_state == GRL_ACQUIRED_AND_BLOCKS_COMMIT && + wsrep_locked_seqno != WSREP_SEQNO_UNDEFINED) { Wsrep_server_state& server_state= Wsrep_server_state::instance(); if (server_state.state() == Wsrep_server_state::s_donor || (WSREP_NNULL(thd) && server_state.state() != Wsrep_server_state::s_synced)) { - /* TODO: maybe redundant here?: */ - wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; server_state.resume(); + wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; } else if (WSREP_NNULL(thd) && server_state.state() == Wsrep_server_state::s_synced) { server_state.resume_and_resync(); + wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED; } } #endif /* WITH_WSREP */ -- cgit v1.2.1 From 8e280f300744ff5e0d7eee0e254dae0ca95e9a5b Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Fri, 21 May 2021 03:38:04 +0200 Subject: MDEV-25719: stunnel uses "verifyChain" without subject checks Another batch of changes that should make the SST process more reliable in all scenarios: 1) Added hostname or CN verification when stunnel is used with certificate chain verification (verifyChain = yes); 2) Added check for the absence of the stunnel utility for mtr tests; 3) Deletion of working files before and after SST is done more accurately; 4) rsync on joiner can be run even if the path to its configuration file contains spaces; 5) More accurate directory creation (for data files and for logs); 6) IST with mysqldump no longer turns off statement logging; 7) Reset password for mysqldump when password is empty but username is specified; 8) More reliable quoting when generating statements in wsrep_sst_mysqldump; 9) Added explicit generation of 2048-bit Diffie-Hellman parameters for sockat < 1.7.3, by analogy with xtrabackup; 10) Compression parameters for qpress are read from all suitable server groups in configuration file, as well as from the [sst] and [xtrabackup] groups; 11) Added a test that checks compression using qpress; 12) Checking for optional utilities is modified to work even if they implemented as built-in shell commands (unlikely on real systems, but more reliable). --- mysql-test/suite/galera/disabled.def | 4 +- mysql-test/suite/galera/include/have_qpress.inc | 4 + mysql-test/suite/galera/include/have_stunnel.inc | 4 + .../galera/r/galera_sst_mariabackup_qpress.result | 4 + mysql-test/suite/galera/suite.pm | 9 +- mysql-test/suite/galera/t/galera_ist_rsync.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin.cnf | 1 - mysql-test/suite/galera/t/galera_log_bin_opt.cnf | 2 +- .../galera/t/galera_sst_mariabackup_qpress.cnf | 12 ++ .../galera/t/galera_sst_mariabackup_qpress.test | 24 ++++ .../t/galera_sst_rsync_encrypt_with_key.test | 1 + .../t/galera_sst_rsync_encrypt_with_server.test | 1 + .../suite/galera/t/galera_wan_restart_sst.test | 3 +- mysql-test/suite/galera_3nodes/suite.pm | 9 ++ scripts/wsrep_sst_common.sh | 88 ++++++++++++-- scripts/wsrep_sst_mariabackup.sh | 129 +++++++++++++-------- scripts/wsrep_sst_mysqldump.sh | 64 +++++----- scripts/wsrep_sst_rsync.sh | 110 ++++++++++++------ 18 files changed, 339 insertions(+), 131 deletions(-) create mode 100644 mysql-test/suite/galera/include/have_qpress.inc create mode 100644 mysql-test/suite/galera/include/have_stunnel.inc create mode 100644 mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index 73c5af3c121..bd7ebd5a95a 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -40,11 +40,11 @@ galera_var_notify_cmd : MDEV-21905 Galera test galera_var_notify_cmd causes hang galera_var_reject_queries : assertion in inline_mysql_socket_send galera_var_replicate_myisam_on : MDEV-24062 Galera test failure on galera_var_replicate_myisam_on galera_var_retry_autocommit: MDEV-18181 Galera test failure on galera.galera_var_retry_autocommit -galera_wan : MDEV-17259 Test failure on galera.galera_wan +#galera_wan : MDEV-17259 Test failure on galera.galera_wan mysql-wsrep#198 : MDEV-24446: galera.mysql-wsrep#198 MTR failed: query 'reap' failed: 2000: Unknown MySQL error partition : MDEV-19958 Galera test failure on galera.partition query_cache: MDEV-15805 Test failure on galera.query_cache -sql_log_bin : MDEV-21491 galera.sql_log_bin +#sql_log_bin : MDEV-21491 galera.sql_log_bin versioning_trx_id: MDEV-18590: galera.versioning_trx_id: Test failure: mysqltest: Result content mismatch galera_wsrep_provider_unset_set: wsrep_provider is read-only for security reasons pxc-421: wsrep_provider is read-only for security reasons diff --git a/mysql-test/suite/galera/include/have_qpress.inc b/mysql-test/suite/galera/include/have_qpress.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_qpress.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/include/have_stunnel.inc b/mysql-test/suite/galera/include/have_stunnel.inc new file mode 100644 index 00000000000..0dd693f2c63 --- /dev/null +++ b/mysql-test/suite/galera/include/have_stunnel.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped as needed +# diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result new file mode 100644 index 00000000000..684a6d00c7e --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -0,0 +1,4 @@ +SELECT 1; +1 +1 +include/assert_grep.inc [Compressed qpress files found] diff --git a/mysql-test/suite/galera/suite.pm b/mysql-test/suite/galera/suite.pm index c0b2da5f349..027100fdb30 100644 --- a/mysql-test/suite/galera/suite.pm +++ b/mysql-test/suite/galera/suite.pm @@ -67,9 +67,16 @@ push @::global_suppressions, qr|WSREP: Wait for gtid returned error 3 while waiting for prior transactions to commit before setting position|, ); +sub which($) { return `sh -c "command -v $_[0]"` } + sub skip_combinations { my %skip = (); - $skip{'include/have_mariabackup.inc'} = 'Need ss' unless `ss -V`; + $skip{'include/have_mariabackup.inc'} = 'Need socket statistics utility' + unless which("lsof") || which("sockstat") || which("ss"); + $skip{'include/have_stunnel.inc'} = "Need 'stunnel' utility" + unless which("stunnel"); + $skip{'include/have_qpress.inc'} = "Need 'qpress' utility" + unless which("qpress"); %skip; } diff --git a/mysql-test/suite/galera/t/galera_ist_rsync.cnf b/mysql-test/suite/galera/t/galera_ist_rsync.cnf index 797e3651967..7a3da6dad02 100644 --- a/mysql-test/suite/galera/t/galera_ist_rsync.cnf +++ b/mysql-test/suite/galera/t/galera_ist_rsync.cnf @@ -10,4 +10,3 @@ wsrep_sync_wait=1 [mysqld.2] wsrep_provider_options='base_port=@mysqld.2.#galera_port;pc.ignore_sb=true' wsrep_sync_wait=1 - diff --git a/mysql-test/suite/galera/t/galera_log_bin.cnf b/mysql-test/suite/galera/t/galera_log_bin.cnf index 8f6a760def0..98e724fb2d0 100644 --- a/mysql-test/suite/galera/t/galera_log_bin.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin.cnf @@ -7,4 +7,3 @@ log-slave-updates [mysqld.2] log-bin log-slave-updates - diff --git a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf index a09efd2e011..3c14cde794c 100644 --- a/mysql-test/suite/galera/t/galera_log_bin_opt.cnf +++ b/mysql-test/suite/galera/t/galera_log_bin_opt.cnf @@ -12,4 +12,4 @@ wsrep_provider_options='base_port=@mysqld.2.#galera_port;gcache.size=1;pc.ignore [sst] transferfmt=@ENV.MTR_GALERA_TFMT -streamfmt=xbstream +streamfmt=mbstream diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf new file mode 100644 index 00000000000..25a9d5fc0b2 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +wsrep_debug=ON + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +compress=quicklz +compress-threads=2 +compress-chunk-size=32768 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test new file mode 100644 index 00000000000..b5386885ca0 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.test @@ -0,0 +1,24 @@ +# +# This test checks that qpress compression works with mariabackup +# Initial SST happens via mariabackup, so there is not much to do in the body +# of the test +# + +--source include/big_test.inc +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source include/have_qpress.inc + +SELECT 1; + +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Confirm that transfer was compressed using qpress algorithm +--let $assert_text = Compressed qpress files found +--let $assert_select = Compressed qpress files found +--let $assert_count = 1 +--let $assert_file = $MYSQLTEST_VARDIR/log/mysqld.2.err +--let $assert_only_after = CURRENT_TEST +--source include/assert_grep.inc diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test index 505f7e7626d..838c473b9ce 100644 --- a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_server.test @@ -1,6 +1,7 @@ --source include/big_test.inc --source include/galera_cluster.inc --source include/have_debug.inc +--source include/have_stunnel.inc # Save original auto_increment_offset values. --let $node_1=node_1 diff --git a/mysql-test/suite/galera/t/galera_wan_restart_sst.test b/mysql-test/suite/galera/t/galera_wan_restart_sst.test index 24620458570..0ded4032103 100644 --- a/mysql-test/suite/galera/t/galera_wan_restart_sst.test +++ b/mysql-test/suite/galera/t/galera_wan_restart_sst.test @@ -4,7 +4,7 @@ # We can not easily restart the first node, so instead we restart all the other nodes. MTR does not allow multiple nodes # to be down at the same time, so restarts are sequential. # -# We can not test any of the actual WAN optimizations from inside MTR and no +# We can not test any of the actual WAN optimizations from inside MTR and no # status variables are provided. So we only check that simple replication works. # @@ -98,7 +98,6 @@ INSERT INTO t1 VALUES (33); INSERT INTO t1 VALUES (341); - # # Check all nodes # diff --git a/mysql-test/suite/galera_3nodes/suite.pm b/mysql-test/suite/galera_3nodes/suite.pm index f93cd0e0909..49b93248a58 100644 --- a/mysql-test/suite/galera_3nodes/suite.pm +++ b/mysql-test/suite/galera_3nodes/suite.pm @@ -67,4 +67,13 @@ push @::global_suppressions, qr|WSREP: Wait for gtid returned error 3 while waiting for prior transactions to commit before setting position|, ); +sub which($) { return `sh -c "command -v $_[0]"` } + +sub skip_combinations { + my %skip = (); + $skip{'include/have_mariabackup.inc'} = 'Need ss' + unless which("lsof") || which("sockstat") || which("ss"); + %skip; +} + bless { }; diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index c98b388a1e2..f4ac2e9936d 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,5 +1,5 @@ -# Copyright (C) 2012-2015 Codership Oy # Copyright (C) 2017-2021 MariaDB +# Copyright (C) 2012-2015 Codership Oy # # 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 @@ -822,14 +822,15 @@ wsrep_log_info() wsrep_cleanup_progress_file() { - [ -n "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true + [ -n "$SST_PROGRESS_FILE" -a \ + -f "$SST_PROGRESS_FILE" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true } wsrep_check_program() { local prog="$1" local cmd=$(command -v "$prog") - if [ ! -x "$cmd" ]; then + if [ -z "$cmd" ]; then echo "'$prog' not found in PATH" return 2 # no such file or directory fi @@ -865,9 +866,9 @@ get_openssl() fi # Let's look for openssl: OPENSSL_BINARY="$(command -v openssl)" - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY='/usr/bin/openssl' - if [ ! -x "$OPENSSL_BINARY" ]; then + if [ -z "$OPENSSL_BINARY" ]; then OPENSSL_BINARY="" fi fi @@ -899,14 +900,14 @@ is_local_ip() [ "$1" = "$(hostname -d)" ] && return 0 local ip_util="$(command -v ip)" - if [ -x "$ip_util" ]; then + if [ -n "$ip_util" ]; then # ip address show ouput format is " inet[6]
/": "$ip_util" address show \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+/" -o \ | grep -F " $1/" >/dev/null && return 0 else local ifconfig_util="$(command -v ifconfig)" - if [ -x "$ifconfig_util" ]; then + if [ -n "$ifconfig_util" ]; then # ifconfig output format is " inet[6]
...": "$ifconfig_util" \ | grep -E "^[[:space:]]*inet.? [^[:space:]]+ " -o \ @@ -923,16 +924,79 @@ check_sockets_utils() sockstat_available=0 ss_available=0 - [ -x "$(command -v lsof)" ] && lsof_available=1 - [ -x "$(command -v sockstat)" ] && sockstat_available=1 - [ -x "$(command -v ss)" ] && ss_available=1 + [ -n "$(command -v lsof)" ] && lsof_available=1 + [ -n "$(command -v sockstat)" ] && sockstat_available=1 + [ -n "$(command -v ss)" ] && ss_available=1 if [ $lsof_available -eq 0 -a \ $sockstat_available -eq 0 -a \ $ss_available -eq 0 ] then - wsrep_log_error "Neither lsof tool, nor ss or sockstat was found in " \ - "the PATH! Make sure you have it installed." + wsrep_log_error "Neither lsof, nor sockstat or ss tool was found in " \ + "the PATH. Make sure you have it installed." exit 2 # ENOENT fi } + +# +# If the ssl_dhparams variable is already set, uses that as a source +# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in +# the datadir, and creates it there if it can't find the file. +# +check_for_dhparams() +{ + if [ -z "$ssl_dhparams" ]; then + ssl_dhparams="$DATA/dhparams.pem" + if [ ! -r "$ssl_dhparams" ]; then + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + wsrep_log_info "Could not find dhparams file, creating $ssl_dhparams" + if ! "$OPENSSL_BINARY" dhparam -out "$ssl_dhparams" 2048 >/dev/null 2>&1 + then + wsrep_log_error "******** ERROR *****************************************" + wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. *" + wsrep_log_error "********************************************************" + ssl_dhparams="" + fi + else + # Rollback: if openssl is not installed, then use + # the default parameters: + ssl_dhparams="" + fi + fi + fi +} + +# +# Compares two version strings. +# The first parameter is the version to be checked; +# The second parameter is the minimum version required; +# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise. +# +check_for_version() +{ + y1=${1#*.} + [ "$y1" = "$1" ] && y1="" + z1=${y1#*.} + [ "$z1" = "$y1" ] && z1="" + x1=${1%%.*} + y1=${y1%%.*} + z1=${z1%%.*} + [ -z "$y1" ] && y1=0 + [ -z "$z1" ] && z1=0 + y2=${2#*.} + [ "$y2" = "$2" ] && y2="" + z2=${y2#*.} + [ "$z2" = "$y2" ] && z2="" + x2=${2%%.*} + y2=${y2%%.*} + z2=${z2%%.*} + [ -z "$y2" ] && y2=0 + [ -z "$z2" ] && z2=0 + [ $x1 -lt $x2 ] && return 1 + [ $x1 -gt $x2 ] && return 0 + [ $y1 -lt $y2 ] && return 1 + [ $y1 -gt $y2 ] && return 0 + [ $z1 -lt $z2 ] && return 1 + return 0 +} diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 899f3eb4f3c..5618c704dbc 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -73,6 +73,12 @@ xtmpdir="" scomp="" sdecomp="" +ssl_dhparams="" + +compress='none' +compress_chunk="" +compress_threads="" + readonly SECRET_TAG="secret" # Required for backup locks @@ -80,25 +86,24 @@ readonly SECRET_TAG="secret" # 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 -if [ -x "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then +if [ -n "$(command -v pv)" ] && pv --help | grep -qw -- '-F'; then pvopts="$pvopts $pvformat" fi pcmd="pv $pvopts" declare -a RC -set +e MARIABACKUP_BIN="$(command -v mariabackup)" if [ ! -x "$MARIABACKUP_BIN" ]; then wsrep_log_error 'mariabackup binary not found in $PATH' exit 42 fi -set -e MBSTREAM_BIN=mbstream DATA="$WSREP_SST_OPT_DATA" INFO_FILE="xtrabackup_galera_info" IST_FILE="xtrabackup_ist" MAGIC_FILE="$DATA/$INFO_FILE" + INNOAPPLYLOG="$DATA/mariabackup.prepare.log" INNOMOVELOG="$DATA/mariabackup.move.log" INNOBACKUPLOG="$DATA/mariabackup.backup.log" @@ -184,7 +189,7 @@ get_keys() ecmd="$ecmd -k '$ekey'" fi elif [ "$eformat" = 'xbcrypt' ]; then - if [ ! -x "$(command -v xbcrypt)" ]; then + if [ -z "$(command -v xbcrypt)" ]; then wsrep_log_error "If encryption using the xbcrypt is enabled, " \ "then you need to install xbcrypt" exit 2 @@ -268,6 +273,22 @@ get_transfer() exit 2 fi + # Determine the socat version + SOCAT_VERSION=$(socat -V 2>&1 | grep -m1 -oe '[0-9]\.[0-9][\.0-9]*') + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" + exit 2 + fi + + if ! check_for_version "$SOCAT_VERSION" "1.7.3"; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: + check_for_dhparams + sockopt=",dhparam='$ssl_dhparams'$sockopt" + fi + if [ $encrypt -eq 2 ]; then wsrep_log_info "Using openssl based encryption with socat: with crt and pem" if [ -z "$tpem" -o -z "$tcert" ]; then @@ -328,7 +349,7 @@ get_footprint() { pushd "$WSREP_SST_OPT_DATA" 1>/dev/null payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress"; then + if [ "$compress" != 'none' ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) @@ -340,7 +361,7 @@ get_footprint() adjust_progress() { - if [ ! -x "$(command -v pv)" ]; then + if [ -z "$(command -v pv)" ]; then wsrep_log_error "pv not found in path: $PATH" wsrep_log_error "Disabling all progress/rate-limiting" pcmd="" @@ -447,6 +468,14 @@ read_cnf() if [ $ssyslog -ne -1 ]; then ssyslog=$(in_config 'mysqld_safe' 'syslog') fi + + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + compress=$(parse_cnf "$encgroups" 'compress' 'none') + if [ "$compress" != 'none' ]; then + compress_chunk=$(parse_cnf "$encgroups" 'compress-chunk-size') + compress_threads=$(parse_cnf "$encgroups" 'compress-threads') + fi + fi } get_stream() @@ -480,7 +509,7 @@ get_proc() sig_joiner_cleanup() { wsrep_log_error "Removing $MAGIC_FILE file due to signal" - rm -f "$MAGIC_FILE" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" } cleanup_joiner() @@ -540,7 +569,7 @@ cleanup_donor() fi fi - rm -f "$DATA/$IST_FILE" || true + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" if [ -n "$progress" -a -p "$progress" ]; then wsrep_log_info "Cleaning up fifo file $progress" @@ -549,13 +578,8 @@ cleanup_donor() wsrep_log_info "Cleaning up temporary directories" - if [ -n "$xtmpdir" ]; then - [ -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true - fi - - if [ -n "$itmpdir" ]; then - [ -d "$itmpdir" ] && rm -rf "$itmpdir" || true - fi + [ -n "$xtmpdir" -a -d "$xtmpdir" ] && rm -rf "$xtmpdir" || true + [ -n "$itmpdir" -a -d "$itmpdir" ] && rm -rf "$itmpdir" || true # Final cleanup pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') @@ -682,12 +706,9 @@ recv_joiner() return fi - pushd "$dir" 1>/dev/null - set +e - local ltcmd="$tcmd" if [ $tmt -gt 0 ]; then - if [ -x "$(command -v timeout)" ]; then + if [ -n "$(command -v timeout)" ]; then if timeout --help | grep -qw -- '-k'; then ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" else @@ -696,6 +717,9 @@ recv_joiner() fi fi + pushd "$dir" 1>/dev/null + set +e + if [ $wait -ne 0 ]; then wait_for_listen "$SST_PORT" "$ADDR" "$MODULE" & fi @@ -781,7 +805,7 @@ monitor_process() wsrep_check_programs "$MARIABACKUP_BIN" -rm -f "$MAGIC_FILE" +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" if [ "$WSREP_SST_OPT_ROLE" != 'joiner' -a "$WSREP_SST_OPT_ROLE" != 'donor' ]; then wsrep_log_error "Invalid role ${WSREP_SST_OPT_ROLE}" @@ -795,13 +819,6 @@ if "$MARIABACKUP_BIN" --help 2>/dev/null | grep -qw -- '--version-check'; then disver='--no-version-check' fi -iopts="$iopts --databases-exclude='lost+found'" - -if [ ${FORCE_FTWRL:-0} -eq 1 ]; then - wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" - iopts="$iopts --no-backup-locks" -fi - # if no command line argument and INNODB_DATA_HOME_DIR environment variable # is not set, try to get it from my.cnf: if [ -z "$INNODB_DATA_HOME_DIR" ]; then @@ -810,19 +827,19 @@ fi OLD_PWD="$(pwd)" +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" if [ $ssyslog -eq 1 ]; then - if [ -x "$(command -v logger)" ]; then + if [ -n "$(command -v logger)" ]; then wsrep_log_info "Logging all stderr of SST/mariabackup to syslog" exec 2> >(logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE) @@ -850,10 +867,8 @@ if [ $sstlogarchive -eq 1 ] then ARCHIVETIMESTAMP=$(date "+%Y.%m.%d-%H.%M.%S.%N") - if [ -n "$sstlogarchivedir" ] - then - if [ ! -d "$sstlogarchivedir" ] - then + if [ -n "$sstlogarchivedir" ]; then + if [ ! -d "$sstlogarchivedir" ]; then mkdir -p "$sstlogarchivedir" fi fi @@ -927,7 +942,6 @@ then if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 if [ -z "$sst_ver" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" @@ -944,6 +958,7 @@ then itmpdir="$(mktemp -d)" wsrep_log_info "Using $itmpdir as mariabackup temporary directory" + usrst=0 if [ -n "$WSREP_SST_OPT_USER" ]; then INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 @@ -1008,6 +1023,25 @@ then tcmd="$ecmd | $tcmd" fi + iopts="$iopts --databases-exclude='lost+found'" + + if [ ${FORCE_FTWRL:-0} -eq 1 ]; then + wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" + iopts="$iopts --no-backup-locks" + fi + + # if compression is enabled for backup files, then add the + # appropriate options to the mariabackup command line: + if [ "$compress" != 'none' ]; then + iopts="$iopts --compress${compress:+=$compress}" + if [ -n "$compress_threads" ]; then + iopts="$iopts --compress-threads=$compress_threads" + fi + if [ -n "$compress_chunk" ]; then + iopts="$iopts --compress-chunk-size=$compress_chunk" + fi + fi + setup_commands set +e timeit "$stagemsg-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" @@ -1082,10 +1116,12 @@ then MODULE="xtrabackup_sst" - rm -f "$DATA/$IST_FILE" + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" # May need xtrabackup_checkpoints later on - rm -f "$DATA/xtrabackup_binary" "$DATA/xtrabackup_galera_info" "$DATA/ib_logfile0" + [ -f "$DATA/xtrabackup_binary" ] && rm -f "$DATA/xtrabackup_binary" + [ -f "$DATA/xtrabackup_galera_info" ] && rm -f "$DATA/xtrabackup_galera_info" + [ -f "$DATA/ib_logfile0" ] && rm -f "$DATA/ib_logfile0" ADDR="$WSREP_SST_OPT_ADDR" @@ -1185,8 +1221,6 @@ then wsrep_log_info "Waiting for SST streaming to complete!" monitor_process $jpid - get_proc - if [ ! -s "$DATA/xtrabackup_checkpoints" ]; then wsrep_log_error "xtrabackup_checkpoints missing, failed mariabackup/SST on donor" exit 2 @@ -1203,11 +1237,15 @@ then if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if [ ! -x "$(command -v qpress)" ]; then - wsrep_log_error "qpress not found in path: $PATH" + if [ -z "$(command -v qpress)" ]; then + wsrep_log_error "qpress utility not found in the path" exit 22 fi + get_proc + + dcmd="xargs -n 2 qpress -dT$nproc" + if [ -n "$progress" ] && pv --help | grep -qw -- '--line-mode'; then count=$(find "$DATA" -type f -name '*.qp' | wc -l) count=$(( count*2 )) @@ -1217,9 +1255,7 @@ then fi pcmd="pv $pvopts" adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi # Decompress the qpress files @@ -1267,6 +1303,7 @@ then MAGIC_FILE="$TDATA/$INFO_FILE" wsrep_log_info "Moving the backup to ${TDATA}" timeit "mariabackup move stage" "$INNOMOVE" + if [ $? -eq 0 ]; then wsrep_log_info "Move successful, removing ${DATA}" rm -rf "$DATA" diff --git a/scripts/wsrep_sst_mysqldump.sh b/scripts/wsrep_sst_mysqldump.sh index e227a888baf..4aa3f8e63d8 100644 --- a/scripts/wsrep_sst_mysqldump.sh +++ b/scripts/wsrep_sst_mysqldump.sh @@ -45,7 +45,12 @@ then exit $EINVAL fi -[ -n "$WSREP_SST_OPT_USER" ] && AUTH="-u$WSREP_SST_OPT_USER" || AUTH= +AUTH="" +usrst=0 +if [ -n "$WSREP_SST_OPT_USER" ]; then + AUTH="-u$WSREP_SST_OPT_USER" + usrst=1 +fi # Refs https://github.com/codership/mysql-wsrep/issues/141 # Passing password in MYSQL_PWD environment variable is considered @@ -56,9 +61,14 @@ fi # whereas (at least on Linux) unprivileged user can't see process environment # that he does not own. So while it may be not secure in the NSA sense of the # word, it is arguably more secure than passing password on the command line. -[ -n "$WSREP_SST_OPT_PSWD" ] && export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD +fi -STOP_WSREP="SET wsrep_on=OFF;" +STOP_WSREP='SET wsrep_on=OFF;' # mysqldump cannot restore CSV tables, fix this issue CSV_TABLES_FIX=" @@ -68,13 +78,13 @@ USE mysql; SET @cond = (SELECT (SUPPORT = 'YES' or SUPPORT = 'DEFAULT') FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'csv'); -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"General log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"General log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; DROP PREPARE stmt; -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"Slow log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"Slow log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; @@ -99,15 +109,15 @@ MYSQL="$MYSQL_CLIENT $WSREP_SST_OPT_CONF "\ # Check if binary logging is enabled on the joiner node. # Note: SELECT cannot be used at this point. -LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL |\ +LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Check the joiner node's server version. -SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL |\ +SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Retrieve the donor's @@global.gtid_binlog_state. -GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL |\ +GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') RESET_MASTER="" @@ -115,7 +125,7 @@ SET_GTID_BINLOG_STATE="" SQL_LOG_BIN_OFF="" # Safety check -if [ "${SERVER_VERSION%%.*}" != '5' ] +if [ ${SERVER_VERSION%%.*} -gt 5 ] then # If binary logging is enabled on the joiner node, we need to copy donor's # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be @@ -135,29 +145,29 @@ MYSQLDUMP="$MYSQLDUMP $WSREP_SST_OPT_CONF $AUTH -S$WSREP_SST_OPT_SOCKET \ --disable-keys --extended-insert --skip-lock-tables --quick --set-charset \ --skip-comments --flush-privileges --all-databases --events" -# need to disable logging when loading the dump -# reason is that dump contains ALTER TABLE for log tables, and -# this causes an error if logging is enabled -GENERAL_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG"` -SLOW_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG"` -$MYSQL -e "$STOP_WSREP SET GLOBAL GENERAL_LOG=OFF" -$MYSQL -e "$STOP_WSREP SET GLOBAL SLOW_QUERY_LOG=OFF" - -# commands to restore log settings -RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" -RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - (echo $STOP_WSREP && echo $RESET_MASTER && \ - echo $SET_GTID_BINLOG_STATE && echo $SQL_LOG_BIN_OFF && \ - echo $STOP_WSREP && $MYSQLDUMP && echo $CSV_TABLES_FIX && \ - echo $RESTORE_GENERAL_LOG && echo $RESTORE_SLOW_QUERY_LOG && \ - echo $SET_START_POSITION && echo $SET_WSREP_GTID_DOMAIN_ID \ + # need to disable logging when loading the dump + # reason is that dump contains ALTER TABLE for log tables, and + # this causes an error if logging is enabled + GENERAL_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG") + SLOW_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG") + + LOG_OFF="SET GLOBAL GENERAL_LOG=OFF; SET GLOBAL SLOW_QUERY_LOG=OFF;" + + # commands to restore log settings + RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" + RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" + + (echo "$STOP_WSREP" && echo "$LOG_OFF" && echo "$RESET_MASTER" && \ + echo "$SET_GTID_BINLOG_STATE" && echo "$SQL_LOG_BIN_OFF" && \ + echo "$STOP_WSREP" && $MYSQLDUMP && echo "$CSV_TABLES_FIX" && \ + echo "$RESTORE_GENERAL_LOG" && echo "$RESTORE_SLOW_QUERY_LOG" && \ + echo "$SET_START_POSITION" && echo "$SET_WSREP_GTID_DOMAIN_ID" \ || echo "SST failed to complete;") | $MYSQL else wsrep_log_info "Bypassing state dump." - echo $SET_START_POSITION | $MYSQL + echo "$SET_START_POSITION" | $MYSQL fi # diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 4f39835e15d..92f77eec331 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -40,13 +40,12 @@ cleanup_joiner() [ "0" != "$RSYNC_REAL_PID" ] && \ kill $RSYNC_REAL_PID && \ sleep 0.5 && \ - kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || \ - : - rm -rf "$RSYNC_CONF" - rm -f "$STUNNEL_CONF" - rm -f "$STUNNEL_PID" - rm -rf "$MAGIC_FILE" - rm -rf "$RSYNC_PID" + kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || : + [ -f "$RSYNC_CONF" ] && rm -f "$RSYNC_CONF" + [ -f "$STUNNEL_CONF" ] && rm -f "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" wsrep_log_info "Joiner cleanup done." if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then wsrep_cleanup_progress_file @@ -125,17 +124,13 @@ check_pid_and_port() } STUNNEL_CONF="$WSREP_SST_OPT_DATA/stunnel.conf" -rm -f "$STUNNEL_CONF" STUNNEL_PID="$WSREP_SST_OPT_DATA/stunnel.pid" -rm -f "$STUNNEL_PID" MAGIC_FILE="$WSREP_SST_OPT_DATA/rsync_sst_complete" -rm -rf "$MAGIC_FILE" BINLOG_TAR_FILE="$WSREP_SST_OPT_DATA/wsrep_sst_binlog.tar" BINLOG_N_FILES=1 -rm -f "$BINLOG_TAR_FILE" || : get_binlog @@ -154,13 +149,13 @@ OLD_PWD="$(pwd)" WSREP_LOG_DIR="$INNODB_LOG_GROUP_HOME" +cd "$WSREP_SST_OPT_DATA" if [ -n "$WSREP_LOG_DIR" ]; then # handle both relative and absolute paths - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$WSREP_LOG_DIR"; cd "$WSREP_LOG_DIR"; pwd -P) -else - # default to datadir - WSREP_LOG_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$WSREP_LOG_DIR" ] && mkdir -p "$WSREP_LOG_DIR" + cd "$WSREP_LOG_DIR" fi +WSREP_LOG_DIR=$(pwd -P) cd "$OLD_PWD" @@ -170,13 +165,13 @@ if [ -z "$INNODB_DATA_HOME_DIR" ]; then INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_DATA_HOME_DIR" ]; then # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_DATA_HOME_DIR"; cd "$INNODB_DATA_HOME_DIR"; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" fi +INNODB_DATA_HOME_DIR=$(pwd -P) cd "$OLD_PWD" @@ -185,13 +180,13 @@ if [ -z "$INNODB_UNDO_DIR" ]; then INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') fi +cd "$WSREP_SST_OPT_DATA" if [ -n "$INNODB_UNDO_DIR" ]; then # handle both relative and absolute paths - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; mkdir -p "$INNODB_UNDO_DIR"; cd "$INNODB_UNDO_DIR"; pwd -P) -else - # default to datadir - INNODB_UNDO_DIR=$(cd "$WSREP_SST_OPT_DATA"; pwd -P) + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" fi +INNODB_UNDO_DIR=$(pwd -P) cd "$OLD_PWD" @@ -239,7 +234,7 @@ if [ -z "$SSLMODE" ]; then # Implicit verification if CA is set and the SSL mode # is not specified by user: if [ -n "$SSTCA" ]; then - if [ -x "$(command -v stunnel)" ]; then + if [ -n "$(command -v stunnel)" ]; then SSLMODE='VERIFY_CA' fi # Require SSL by default if SSL key and cert are present: @@ -260,28 +255,36 @@ then case "$SSLMODE" in 'VERIFY_IDENTITY') VERIFY_OPT='verifyPeer = yes' + CHECK_OPT="" ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT='checkHost = localhost' + else + CHECK_OPT='checkHost = $WSREP_SST_OPT_HOST_UNESCAPED' + fi ;; *) wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" exit 22 # EINVAL esac - if [ -z "$CAFILE_OPT" ] - then - wsrep_log_error "Can't have ssl-mode=$SSLMODE without CA file" + if [ -z "$CAFILE_OPT" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" exit 22 # EINVAL fi else VERIFY_OPT="" + CHECK_OPT="" fi STUNNEL="" -if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ] && wsrep_check_programs stunnel -then - wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', SSLMODE: '$SSLMODE'" - STUNNEL="stunnel $STUNNEL_CONF" +if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then + STUNNEL_BIN="$(command -v stunnel)" + if [ -n "$STUNNEL_BIN" ]; then + wsrep_log_info "Using stunnel for SSL encryption: CAfile: '$SSTCA', ssl-mode='$SSLMODE'" + STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + fi fi readonly SECRET_TAG="secret" @@ -289,7 +292,13 @@ readonly SECRET_TAG="secret" if [ "$WSREP_SST_OPT_ROLE" = 'donor' ] then -cat << EOF > "$STUNNEL_CONF" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" + + if [ -n "$STUNNEL" ] + then + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} @@ -300,7 +309,9 @@ client = yes connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 ${VERIFY_OPT} +${CHECK_OPT} EOF + fi if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then @@ -366,7 +377,7 @@ EOF # first, the normal directories, so that we can detect incompatible protocol RC=0 - eval rsync ${STUNNEL:+--rsh=\"$STUNNEL\"} \ + eval rsync ${STUNNEL:+"'--rsh=$STUNNEL'"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ $WHOLE_FILE_OPT $FILTER "'$WSREP_SST_OPT_DATA/'" \ @@ -449,7 +460,7 @@ EOF fi - echo "continue" # now server can resume updating data + echo 'continue' # now server can resume updating data echo "$STATE" > "$MAGIC_FILE" @@ -487,7 +498,10 @@ then wsrep_log_error "rsync daemon already running." exit 114 # EALREADY fi - rm -rf "$RSYNC_PID" + + [ -f "$RSYNC_PID" ] && rm -f "$RSYNC_PID" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" ADDR="$WSREP_SST_OPT_ADDR" RSYNC_PORT="$WSREP_SST_OPT_PORT" @@ -541,20 +555,40 @@ EOF rsync --daemon --no-detach --port "$RSYNC_PORT" --config "$RSYNC_CONF" $RSYNC_EXTRA_ARGS & RSYNC_REAL_PID=$! else - cat << EOF > "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm -f "$STUNNEL_PID" + # Let's check if the path to the config file contains a space? + if [ "${RSYNC_CONF#* }" = "$RSYNC_CONF" ]; then + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT ${CAFILE_OPT} foreground = yes pid = $STUNNEL_PID debug = warning -debug = 6 client = no [rsync] accept = $STUNNEL_ACCEPT exec = $(command -v rsync) execargs = rsync --server --daemon --config=$RSYNC_CONF . EOF + else + # The path contains a space, so we will run it via + # shell with "eval" command: + export RSYNC_CMD="eval $(command -v rsync) --server --daemon --config='$RSYNC_CONF' ." + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +foreground = yes +pid = $STUNNEL_PID +debug = warning +client = no +[rsync] +accept = $STUNNEL_ACCEPT +exec = $SHELL +execargs = $SHELL -c \$RSYNC_CMD +EOF + fi stunnel "$STUNNEL_CONF" & RSYNC_REAL_PID=$! RSYNC_PID="$STUNNEL_PID" @@ -655,6 +689,6 @@ else exit 22 # EINVAL fi -rm -f "$BINLOG_TAR_FILE" || : +[ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" exit 0 -- cgit v1.2.1 From 2087d47aaeadc06dd007ce9bd28984ecc8e2101e Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Fri, 21 May 2021 17:46:48 +0300 Subject: MDEV-22462: Item_in_subselect::create_single_in_to_exists_cond(JOIN *, Item **, Item **): Assertion `false' failed. Item_in_subselect::create_single_in_to_exists_cond() should handle the case where the subquery is a table-less select but it is not a result of a UNION. (Table-less subqueries like "(SELECT 1)" are "substituted" with their select list, but table-less subqueries with WHERE or HAVING clause, like "(SELECT 1 WHERE ...)" are not substituted. They are handled with regular execution path) --- mysql-test/r/subselect4.result | 11 ++++++++++- mysql-test/t/subselect4.test | 13 ++++++++++++- sql/item_subselect.cc | 44 ++++++++++++++++++++---------------------- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/mysql-test/r/subselect4.result b/mysql-test/r/subselect4.result index 2a691799be5..b1db309ec18 100644 --- a/mysql-test/r/subselect4.result +++ b/mysql-test/r/subselect4.result @@ -2721,7 +2721,15 @@ id select_type table type possible_keys key key_len ref rows Extra SELECT a FROM t1 WHERE (a, a) IN (SELECT 1, 2) AND a = (SELECT MIN(b) FROM t2); a DROP TABLE t1,t2; -# End of 10.2 tests +# +# MDEV-22462: Item_in_subselect::create_single_in_to_exists_cond(JOIN *, Item **, Item **): Assertion `false' failed. +# +select 1 from dual where 1 in (select 5 from dual where 1); +1 +create table t1 (a int); +insert into t1 values (1),(2),(3); +update t1 set a = 2 where a in (select a from dual where a = a); +drop table t1; # # MDEV-24925: Server crashes in Item_subselect::init_expr_cache_tracker # @@ -2793,3 +2801,4 @@ FROM (t1 JOIN t1 AS ref_t1 ON (t1.i1 > (SELECT ref_t1.i1 AS c0 FROM t1 b ORDER BY -c0))); ERROR 21000: Subquery returns more than 1 row DROP TABLE t1; +# End of 10.2 tests diff --git a/mysql-test/t/subselect4.test b/mysql-test/t/subselect4.test index 58aa7868815..bd1e20cb5d6 100644 --- a/mysql-test/t/subselect4.test +++ b/mysql-test/t/subselect4.test @@ -2236,7 +2236,17 @@ SELECT a FROM t1 WHERE (a, a) IN (SELECT 1, 2) AND a = (SELECT MIN(b) FROM t2); DROP TABLE t1,t2; ---echo # End of 10.2 tests +--echo # +--echo # MDEV-22462: Item_in_subselect::create_single_in_to_exists_cond(JOIN *, Item **, Item **): Assertion `false' failed. +--echo # + +select 1 from dual where 1 in (select 5 from dual where 1); + +create table t1 (a int); +insert into t1 values (1),(2),(3); + +update t1 set a = 2 where a in (select a from dual where a = a); +drop table t1; --echo # --echo # MDEV-24925: Server crashes in Item_subselect::init_expr_cache_tracker @@ -2296,3 +2306,4 @@ FROM (t1 JOIN t1 AS ref_t1 ON DROP TABLE t1; +--echo # End of 10.2 tests diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index ed8e5e900a2..1e3c9a77a26 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -2249,7 +2249,8 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, */ Item *item= (Item*) select_lex->item_list.head(); - if (select_lex->table_list.elements) + if (select_lex->table_list.elements || + !(select_lex->master_unit()->is_union())) { Item *having= item; Item *orig_item= item; @@ -2297,31 +2298,28 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN *join, } else { - if (select_lex->master_unit()->is_union()) + DBUG_ASSERT(select_lex->master_unit()->is_union()); + + Item *new_having= + func->create(thd, expr, + new (thd->mem_root) Item_ref_null_helper(thd, + &select_lex->context, + this, + &select_lex->ref_pointer_array[0], + (char *)"", + (char *)"")); + if (!abort_on_null && left_expr->maybe_null) { - Item *new_having= - func->create(thd, expr, - new (thd->mem_root) Item_ref_null_helper(thd, - &select_lex->context, - this, - &select_lex->ref_pointer_array[0], - (char *)"", - (char *)"")); - if (!abort_on_null && left_expr->maybe_null) - { - disable_cond_guard_for_const_null_left_expr(0); - if (!(new_having= new (thd->mem_root) Item_func_trig_cond(thd, new_having, - get_cond_guard(0)))) - DBUG_RETURN(true); - } - - new_having->name= (char*) in_having_cond; - if (fix_having(new_having, select_lex)) + disable_cond_guard_for_const_null_left_expr(0); + if (!(new_having= new (thd->mem_root) Item_func_trig_cond(thd, new_having, + get_cond_guard(0)))) DBUG_RETURN(true); - *having_item= new_having; } - else - DBUG_ASSERT(false); + + new_having->name= (char*) in_having_cond; + if (fix_having(new_having, select_lex)) + DBUG_RETURN(true); + *having_item= new_having; } } -- cgit v1.2.1 From 9739cf1859aaf08b88a3df1a7dc56eb514f63fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 21 May 2021 18:50:30 +0300 Subject: MDEV-25664 Potential hang in purge for virtual columns ha_innobase::open(): If the table is only being opened by purge for evaluating virtual column values, avoid invoking initialize_auto_increment(), because the purge thread may already be holding an shared latch on the clustered index root page. Shared latches are not recursive. The additional request would lead to a hang if another thread has started waiting for an exclusive latch. --- storage/innobase/handler/ha_innodb.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 8bdc0a9e478..b7c6fa98b1c 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6488,7 +6488,9 @@ no_such_table: dict_table_get_format(m_prebuilt->table)); } - if (m_prebuilt->table == NULL + const my_bool for_vc_purge = THDVAR(thd, background_thread); + + if (for_vc_purge || m_prebuilt->table == NULL || dict_table_is_temporary(m_prebuilt->table) || m_prebuilt->table->persistent_autoinc || !m_prebuilt->table->is_readable()) { @@ -6512,7 +6514,7 @@ no_such_table: } } - if (!THDVAR(thd, background_thread)) { + if (!for_vc_purge) { info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); } -- cgit v1.2.1 From 43c9fcefc07d2f42b65950e76adfbc3c1e8acb28 Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Wed, 12 May 2021 19:32:29 -0700 Subject: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist In the code existed just before this patch binding of a table reference to the specification of the corresponding CTE happens in the function open_and_process_table(). If the table reference is not the first in the query the specification is cloned in the same way as the specification of a view is cloned for any reference of the view. This works fine for standalone queries, but does not work for stored procedures / functions for the following reason. When the first call of a stored procedure/ function SP is processed the body of SP is parsed. When a query of SP is parsed the info on each encountered table reference is put into a TABLE_LIST object linked into a global chain associated with the query. When parsing of the query is finished the basic info on the table references from this chain except table references to derived tables and information schema tables is put in one hash table associated with SP. When parsing of the body of SP is finished this hash table is used to construct TABLE_LIST objects for all table references mentioned in SP and link them into the list of such objects passed to a pre-locking process that calls open_and_process_table() for each table from the list. When a TABLE_LIST for a view is encountered the view is opened and its specification is parsed. For any table reference occurred in the specification a new TABLE_LIST object is created to be included into the list for pre-locking. After all objects in the pre-locking have been looked through the tables mentioned in the list are locked. Note that the objects referenced CTEs are just skipped here as it is impossible to resolve these references without any info on the context where they occur. Now the statements from the body of SP are executed one by one that. At the very beginning of the execution of a query the tables used in the query are opened and open_and_process_table() now is called for each table reference mentioned in the list of TABLE_LIST objects associated with the query that was built when the query was parsed. For each table reference first the reference is checked against CTEs definitions in whose scope it occurred. If such definition is found the reference is considered resolved and if this is not the first reference to the found CTE the the specification of the CTE is re-parsed and the result of the parsing is added to the parsing tree of the query as a sub-tree. If this sub-tree contains table references to other tables they are added to the list of TABLE_LIST objects associated with the query in order the referenced tables to be opened. When the procedure that opens the tables comes to the TABLE_LIST object created for a non-first reference to a CTE it discovers that the referenced table instance is not locked and reports an error. Thus processing non-first table references to a CTE similar to how references to view are processed does not work for queries used in stored procedures / functions. And the main problem is that the current pre-locking mechanism employed for stored procedures / functions does not allow to save the context in which a CTE reference occur. It's not trivial to save the info about the context where a CTE reference occurs while the resolution of the table reference cannot be done without this context and consequentially the specification for the table reference cannot be determined. This patch solves the above problem by moving resolution of all CTE references at the parsing stage. More exactly references to CTEs occurred in a query are resolved right after parsing of the query has finished. After resolution any CTE reference it is marked as a reference to to derived table. So it is excluded from the hash table created for pre-locking used base tables and view when the first call of a stored procedure / function is processed. This solution required recursive calls of the parser. The function THD::sql_parser() has been added specifically for recursive invocations of the parser. --- mysql-test/r/cte_nonrecursive.result | 213 ++++++++++++++++- mysql-test/r/cte_recursive.result | 8 +- mysql-test/t/cte_nonrecursive.test | 202 ++++++++++++++++ sql/item_subselect.cc | 1 - sql/sp_head.cc | 3 +- sql/sql_base.cc | 33 +-- sql/sql_class.cc | 54 +++++ sql/sql_class.h | 9 +- sql/sql_cte.cc | 437 ++++++++++++++++++++++++----------- sql/sql_cte.h | 84 ++++++- sql/sql_lex.cc | 3 + sql/sql_lex.h | 27 ++- sql/sql_parse.cc | 16 +- sql/sql_prepare.cc | 3 - sql/sql_view.cc | 9 - sql/sql_yacc.yy | 66 +++++- sql/table.h | 37 +++ 17 files changed, 987 insertions(+), 218 deletions(-) diff --git a/mysql-test/r/cte_nonrecursive.result b/mysql-test/r/cte_nonrecursive.result index da954c1f14b..7c6c6e8dedd 100644 --- a/mysql-test/r/cte_nonrecursive.result +++ b/mysql-test/r/cte_nonrecursive.result @@ -606,7 +606,7 @@ with t(c) as (select a from t1 where b >= 'c') select * from t r1 where r1.c=4; show create view v3; View Create View character_set_client collation_connection -v3 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v3` AS with t as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c` from `t` `r1` where `r1`.`c` = 4 latin1 latin1_swedish_ci +v3 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v3` AS with t(c) as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c` from `t` `r1` where `r1`.`c` = 4 latin1 latin1_swedish_ci select * from v3; c 4 @@ -618,7 +618,7 @@ with t(c) as (select a from t1 where b >= 'c') select * from t r1, t r2 where r1.c=r2.c and r2.c=4; show create view v4; View Create View character_set_client collation_connection -v4 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v4` AS with t as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c`,`r2`.`c` AS `d` from (`t` `r1` join `t` `r2`) where `r1`.`c` = `r2`.`c` and `r2`.`c` = 4 latin1 latin1_swedish_ci +v4 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v4` AS with t(c) as (select `test`.`t1`.`a` AS `c` from `test`.`t1` where `test`.`t1`.`b` >= 'c')select `r1`.`c` AS `c`,`r2`.`c` AS `d` from (`t` `r1` join `t` `r2`) where `r1`.`c` = `r2`.`c` and `r2`.`c` = 4 latin1 latin1_swedish_ci select * from v4; c d 4 4 @@ -1120,10 +1120,10 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 2 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where 5 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL -6 UNION ALL NULL NULL NULL NULL 14 100.00 -9 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where -12 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where -NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL +6 UNION ALL NULL NULL NULL NULL 14 100.00 +14 DERIVED t1 ALL NULL NULL NULL NULL 7 100.00 Using where +11 UNION t1 ALL NULL NULL NULL NULL 7 100.00 Using where +NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: Note 1003 with cte_e as (with cte_o as (with cte_i as (select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 7)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 1)select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` < 3 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1 union select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` > 4 and `test`.`t1`.`a` > 1 and `test`.`t1`.`a` < 7 and `test`.`t1`.`a` > 1)select `cte_e1`.`a` AS `a` from `cte_e` `cte_e1` where `cte_e1`.`a` > 1 union select `cte_e2`.`a` AS `a` from `cte_e` `cte_e2` @@ -1763,4 +1763,205 @@ a c 2 1 7 3 drop table t1; +# +# MDEV-23886: Stored Function returning the result of a query +# that uses CTE over a table twice +# +create table t1 (c1 int); +insert into t1 values (1),(2),(6); +create function f1() returns int return +( with cte1 as (select c1 from t1) +select sum(c1) from +(select * from cte1 union all select * from cte1) dt +); +select f1(); +f1() +18 +create function f2() returns int return +( with cte1 as (select c1 from t1) +select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); +f2() +9 +create function f3() returns int return +( with cte1 as (select c1 from t1) +select +case +when exists(select 1 from cte1 where c1 between 1 and 2) then 1 +when exists(select 1 from cte1 where c1 between 5 and 6) then 2 +else 0 +end +); +select f3(); +f3() +1 +create view v1 as (select c1 from t1); +create function f4() returns int return +( select sum(c1) from +(select * from v1 union all select * from v1) dt +); +select f4(); +f4() +18 +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); +f5() +9 +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; +create function f6() returns int return +(select s from v2); +select f6(); +f6() +18 +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); +f7() +18 +select f5() + f6(); +f5() + f6() +27 +prepare stmt from "select f5() + f6();"; +execute stmt; +f5() + f6() +27 +execute stmt; +f5() + f6() +27 +deallocate prepare stmt; +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; +drop view v1; +drop view v2; +create table t2 (a int, b int); +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; +select * from t2; +a b +6 6 +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +drop procedure p1; +# checking CTE resolution for queries with hanging CTEs +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; +a b +1 2 +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +ERROR 42S22: Unknown column 'c1' in 'where clause' +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; +ERROR 42S22: Unknown column 'cte2.c1' in 'where clause' +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; +a b +1 1 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; +a b +6 6 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; +ERROR 23000: Column 'c1' in where clause is ambiguous +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; +a b +1 1 +2 1 +1 2 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; +a b +6 6 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; +ERROR 42S22: Unknown column 'r1.c1' in 'field list' +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +2 2 +drop procedure p1; +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +ERROR 42S22: Unknown column 'a' in 'field list' +drop procedure p1; +drop table t1,t2; # End of 10.2 tests diff --git a/mysql-test/r/cte_recursive.result b/mysql-test/r/cte_recursive.result index b6b4ed7fb37..3e926525e99 100644 --- a/mysql-test/r/cte_recursive.result +++ b/mysql-test/r/cte_recursive.result @@ -699,7 +699,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL 2 DERIVED ALL NULL NULL NULL NULL 12 100.00 Using where Warnings: -Note 1003 with recursive ancestor_couple_ids as (select `a`.`father` AS `h_id`,`a`.`mother` AS `w_id` from `coupled_ancestors` `a` where `a`.`father` is not null and `a`.`mother` is not null), coupled_ancestors as (select `test`.`folks`.`id` AS `id`,`test`.`folks`.`name` AS `name`,`test`.`folks`.`dob` AS `dob`,`test`.`folks`.`father` AS `father`,`test`.`folks`.`mother` AS `mother` from `test`.`folks` where `test`.`folks`.`name` = 'Me' union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `fa` where `test`.`p`.`id` = `fa`.`h_id` union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `ma` where `test`.`p`.`id` = `ma`.`w_id`)select `h`.`name` AS `name`,`h`.`dob` AS `dob`,`w`.`name` AS `name`,`w`.`dob` AS `dob` from `ancestor_couple_ids` `c` join `coupled_ancestors` `h` join `coupled_ancestors` `w` where `h`.`id` = `c`.`h_id` and `w`.`id` = `c`.`w_id` +Note 1003 with recursive ancestor_couple_ids(h_id,w_id) as (select `a`.`father` AS `h_id`,`a`.`mother` AS `w_id` from `coupled_ancestors` `a` where `a`.`father` is not null and `a`.`mother` is not null), coupled_ancestors(id,name,dob,father,mother) as (select `test`.`folks`.`id` AS `id`,`test`.`folks`.`name` AS `name`,`test`.`folks`.`dob` AS `dob`,`test`.`folks`.`father` AS `father`,`test`.`folks`.`mother` AS `mother` from `test`.`folks` where `test`.`folks`.`name` = 'Me' union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `fa` where `test`.`p`.`id` = `fa`.`h_id` union all select `test`.`p`.`id` AS `id`,`test`.`p`.`name` AS `name`,`test`.`p`.`dob` AS `dob`,`test`.`p`.`father` AS `father`,`test`.`p`.`mother` AS `mother` from `test`.`folks` `p` join `ancestor_couple_ids` `ma` where `test`.`p`.`id` = `ma`.`w_id`)select `h`.`name` AS `name`,`h`.`dob` AS `dob`,`w`.`name` AS `name`,`w`.`dob` AS `dob` from `ancestor_couple_ids` `c` join `coupled_ancestors` `h` join `coupled_ancestors` `w` where `h`.`id` = `c`.`h_id` and `w`.`id` = `c`.`w_id` # simple mutual recursion with recursive ancestor_couple_ids(h_id, w_id) @@ -3047,7 +3047,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 4 DEPENDENT SUBQUERY ALL NULL NULL NULL NULL 16 100.00 Using where NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: -Note 1003 with recursive destinations as (select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union select `test`.`b`.`arrival` AS `arrival`,`r`.`legs` + 1 AS `r.legs + 1` from `destinations` `r` join `test`.`flights` `b` where `r`.`city` = `test`.`b`.`departure` and !(`test`.`b`.`arrival`,(select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations` +Note 1003 with recursive destinations(city,legs) as (select `test`.`a`.`arrival` AS `city`,1 AS `legs` from `test`.`flights` `a` where `test`.`a`.`departure` = 'Cairo' union select `test`.`b`.`arrival` AS `arrival`,`r`.`legs` + 1 AS `r.legs + 1` from `destinations` `r` join `test`.`flights` `b` where `r`.`city` = `test`.`b`.`departure` and !(`test`.`b`.`arrival`,(select `destinations`.`city` from `destinations` where trigcond(`test`.`b`.`arrival` = `destinations`.`city` or `destinations`.`city` is null) having trigcond(`destinations`.`city` is null))))select `destinations`.`city` AS `city`,`destinations`.`legs` AS `legs` from `destinations` set standard_compliant_cte=default; drop table flights; # @@ -3334,7 +3334,7 @@ NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL 5 DERIVED ALL NULL NULL NULL NULL 2 100.00 Using where 5 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join) Warnings: -Note 1003 with recursive rcte as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte1`.`c1` AS `c1`,`cte2`.`c2` AS `c2` from `cte1` join `cte2` +Note 1003 with recursive rcte(a) as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte1`.`c1` AS `c1`,`cte2`.`c2` AS `c2` from `cte1` join `cte2` prepare stmt from "with recursive rcte(a) as (select 1 union select cast(a+1 as unsigned) from rcte where a < 10), @@ -3420,7 +3420,7 @@ NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL 4 DERIVED ALL NULL NULL NULL NULL 2 100.00 Using where 4 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join) Warnings: -Note 1003 with recursive rcte as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte2`.`c2` AS `c2`,`cte1`.`c1` AS `c1` from `cte2` join `cte1` +Note 1003 with recursive rcte(a) as (select 1 AS `a` union select cast(`rcte`.`a` + 1 as unsigned) AS `cast(a+1 as unsigned)` from `rcte` where `rcte`.`a` < 10), cte1 as (select count(0) AS `c1` from `rcte` join `test`.`t1` where `rcte`.`a` between 3 and 5 and `test`.`t1`.`id` = `rcte`.`a` - 3), cte2 as (select count(0) AS `c2` from `rcte` join `test`.`t1` where `rcte`.`a` between 7 and 8 and `test`.`t1`.`id` = `rcte`.`a` - 7)select `cte2`.`c2` AS `c2`,`cte1`.`c1` AS `c1` from `cte2` join `cte1` prepare stmt from "with recursive rcte(a) as (select 1 union select cast(a+1 as unsigned) from rcte where a < 10), diff --git a/mysql-test/t/cte_nonrecursive.test b/mysql-test/t/cte_nonrecursive.test index c2ebc92c858..f994781548f 100644 --- a/mysql-test/t/cte_nonrecursive.test +++ b/mysql-test/t/cte_nonrecursive.test @@ -1261,4 +1261,206 @@ select a, c from cte as r2 where a > 4; drop table t1; +--echo # +--echo # MDEV-23886: Stored Function returning the result of a query +--echo # that uses CTE over a table twice +--echo # + +create table t1 (c1 int); +insert into t1 values (1),(2),(6); + +create function f1() returns int return +( with cte1 as (select c1 from t1) + select sum(c1) from + (select * from cte1 union all select * from cte1) dt +); +select f1(); + +create function f2() returns int return +( with cte1 as (select c1 from t1) + select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); + +create function f3() returns int return +( with cte1 as (select c1 from t1) + select + case + when exists(select 1 from cte1 where c1 between 1 and 2) then 1 + when exists(select 1 from cte1 where c1 between 5 and 6) then 2 + else 0 + end +); +select f3(); + +create view v1 as (select c1 from t1); + +create function f4() returns int return +( select sum(c1) from + (select * from v1 union all select * from v1) dt +); +select f4(); + +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); + +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; + +create function f6() returns int return +(select s from v2); +select f6(); + +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); + +select f5() + f6(); + +prepare stmt from "select f5() + f6();"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; + +drop view v1; +drop view v2; + +create table t2 (a int, b int); + +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; + +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +--echo # checking CTE resolution for queries with hanging CTEs + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; + +select * from t2; + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; + +--error ER_NON_UNIQ_ERROR +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +--error ER_BAD_FIELD_ERROR +call p1(); + +drop procedure p1; + +drop table t1,t2; + --echo # End of 10.2 tests diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 1e3c9a77a26..25621dfe104 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1774,7 +1774,6 @@ double Item_in_subselect::val_real() As far as Item_in_subselect called only from Item_in_optimizer this method should not be used */ - DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); if (forced_const) return value; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index b205d253733..6a6bdf8fe09 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3087,8 +3087,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, #endif if (open_tables) - res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) || - instr->exec_open_and_lock_tables(thd, m_lex->query_tables); + res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); if (!res) { diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e3c30c7433b..c9a34221544 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3406,7 +3406,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, if (tables->derived) { if (!tables->view) + { + if (!tables->is_derived()) + tables->set_derived(); goto end; + } /* We restore view's name and database wiped out by derived tables processing and fall back to standard open process in order to @@ -3418,35 +3422,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, tables->table_name= tables->view_name.str; tables->table_name_length= tables->view_name.length; } - else if (tables->select_lex) - { - /* - Check whether 'tables' refers to a table defined in a with clause. - If so set the reference to the definition in tables->with. - */ - if (!tables->with) - tables->with= tables->select_lex->find_table_def_in_with_clauses(tables); - /* - If 'tables' is defined in a with clause set the pointer to the - specification from its definition in tables->derived. - */ - if (tables->with) - { - if (tables->is_recursive_with_table() && - !tables->is_with_table_recursive_reference()) - { - tables->with->rec_outer_references++; - With_element *with_elem= tables->with; - while ((with_elem= with_elem->get_next_mutually_recursive()) != - tables->with) - with_elem->rec_outer_references++; - } - if (tables->set_as_with_table(thd, tables->with)) - DBUG_RETURN(1); - else - goto end; - } - } if (!tables->derived && is_infoschema_db(tables->db, tables->db_length)) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ac9df47dd47..80ff0aa077c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2525,6 +2525,60 @@ void THD::close_active_vio() #endif +/* + @brief MySQL parser used for recursive invocations + + @param old_lex The LEX structure in the state when this parser + is called recursively + @param lex The LEX structure used to parse a new SQL fragment + @param str The SQL fragment to parse + @param str_len The length of the SQL fragment to parse + @param stmt_prepare_mode true <=> when parsing a prepare statement + + @details + This function is to be used when parsing of an SQL fragment is + needed within one of the grammar rules. + + @notes + Currently the function is used only when the specification of a CTE + is parsed for the not first and not recursive references of the CTE. + + @retval false On a successful parsing of the fragment + @retval true Otherwise +*/ + +bool THD::sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode) +{ + extern int MYSQLparse(THD * thd); + + bool parse_status= false; + Parser_state parser_state; + Parser_state *old_parser_state= m_parser_state; + + if (parser_state.init(this, str, str_len)) + return true; + + m_parser_state= &parser_state; + parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; + parser_state.m_lip.multi_statements= false; + parser_state.m_lip.m_digest= NULL; + + lex->param_list= old_lex->param_list; + lex->sphead= old_lex->sphead; + lex->spname= old_lex->spname; + lex->spcont= old_lex->spcont; + lex->sp_chistics= old_lex->sp_chistics; + lex->trg_chistics= old_lex->trg_chistics; + + parse_status= MYSQLparse(this) != 0; + + m_parser_state= old_parser_state; + + return parse_status; +} + + struct Item_change_record: public ilink { Item **place; diff --git a/sql/sql_class.h b/sql/sql_class.h index 472d6294cf8..50ab3c56ca9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4002,14 +4002,11 @@ public: to resolve all CTE names as we don't need this message to be thrown for any CTE references. */ - if (!lex->with_clauses_list) + if (!lex->with_cte_resolution) { my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); return TRUE; } - /* This will allow to throw an error later for non-CTE references */ - *p_db= NULL; - *p_db_length= 0; } else { @@ -4545,6 +4542,10 @@ public: current_linfo= 0; mysql_mutex_unlock(&LOCK_thread_count); } + + bool sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode); + }; inline void add_to_active_threads(THD *thd) diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index 3a2301a5730..9dad33d5b71 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -58,7 +58,7 @@ bool With_clause::add_with_element(With_element *elem) true on failure */ -bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) +bool LEX::check_dependencies_in_with_clauses() { for (With_clause *with_clause= with_clauses_list; with_clause; @@ -74,6 +74,200 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) } +/** + @brief + Resolve references to CTE in specification of hanging CTE + + @details + A CTE to which there are no references in the query is called hanging CTE. + Although such CTE is not used for execution its specification must be + subject to context analysis. All errors concerning references to + non-existing tables or fields occurred in the specification must be + reported as well as all other errors caught at the prepare stage. + The specification of a hanging CTE might contain references to other + CTE outside of the specification and within it if the specification + contains a with clause. This function resolves all such references for + all hanging CTEs encountered in the processed query. + + @retval + false on success + true on failure +*/ + +bool +LEX::resolve_references_to_cte_in_hanging_cte() +{ + for (With_clause *with_clause= with_clauses_list; + with_clause; with_clause= with_clause->next_with_clause) + { + for (With_element *with_elem= with_clause->with_list.first; + with_elem; with_elem= with_elem->next) + { + if (!with_elem->is_referenced()) + { + TABLE_LIST *first_tbl= + with_elem->spec->first_select()->table_list.first; + TABLE_LIST **with_elem_end_pos= with_elem->head->tables_pos.end_pos; + if (first_tbl && resolve_references_to_cte(first_tbl, with_elem_end_pos)) + return true; + } + } + } + return false; +} + + +/** + @brief + Resolve table references to CTE from a sub-chain of table references + + @param tables Points to the beginning of the sub-chain + @param tables_last Points to the address with the sub-chain barrier + + @details + The method resolves tables references to CTE from the chain of + table references specified by the parameters 'tables' and 'tables_last'. + It resolves the references against the CTE definition occurred in a query + or the specification of a CTE whose parsing tree is represented by + this LEX structure. The method is always called right after the process + of parsing the query or of the specification of a CTE has been finished, + thus the chain of table references used in the parsed fragment has been + already built. It is assumed that parameters of the method specify a + a sub-chain of this chain. + If a table reference can be potentially a table reference to a CTE and it + has not been resolved yet then the method tries to find the definition + of the CTE against which the reference can be resolved. If it succeeds + it sets the field TABLE_LIST::with to point to the found definition. + It also sets the field TABLE_LIST::derived to point to the specification + of the found CTE and sets TABLE::db.str to empty_c_string. This will + allow to handle this table reference like a reference to a derived handle. + If another table reference has been already resolved against this CTE + and this CTE is not recursive then a clone of the CTE specification is + constructed using the function With_element::clone_parsed_spec() and + TABLE_LIST::derived is set to point to this clone rather than to the + original specification. + If the method does not find a matched CTE definition in the parsed fragment + then in the case when the flag this->only_cte_resolution is set to true + it just moves to the resolution of the next table reference from the + specified sub-chain while in the case when this->only_cte_resolution is set + to false the method additionally sets an mdl request for this table + reference. + + @notes + The flag this->only_cte_resolution is set to true in the cases when + the failure to resolve a table reference as a CTE reference within + the fragment associated with this LEX structure does not imply that + this table reference cannot be resolved as such at all. + + @retval false On success: no errors reported, no memory allocations failed + @retval true Otherwise +*/ + +bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last) +{ + With_element *with_elem= 0; + + for (TABLE_LIST *tbl= tables; tbl != *tables_last; tbl= tbl->next_global) + { + if (tbl->derived) + continue; + if (!tbl->db && !tbl->with) + tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl); + if (!tbl->with) // no CTE matches table reference tbl + { + if (only_cte_resolution) + continue; + if (!tbl->db) // no database specified in table reference tbl + { + if (!thd->db) // no default database is set + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + return true; + } + if (copy_db_to(&tbl->db, &tbl->db_length)) + return true; + if (!(tbl->table_options & TL_OPTION_ALIAS)) + tbl->mdl_request.init(MDL_key::TABLE, tbl->db, + tbl->table_name, + tbl->mdl_type, MDL_TRANSACTION); + tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); + } + continue; + } + with_elem= tbl->with; + if (tbl->is_recursive_with_table() && + !tbl->is_with_table_recursive_reference()) + { + tbl->with->rec_outer_references++; + while ((with_elem= with_elem->get_next_mutually_recursive()) != + tbl->with) + with_elem->rec_outer_references++; + } + if (!with_elem->is_used_in_query || with_elem->is_recursive) + { + tbl->derived= with_elem->spec; + if (tbl->derived != tbl->select_lex->master_unit() && + !with_elem->is_recursive && + !tbl->is_with_table_recursive_reference()) + { + tbl->derived->move_as_slave(tbl->select_lex); + } + with_elem->is_used_in_query= true; + } + else + { + if (!(tbl->derived= tbl->with->clone_parsed_spec(thd->lex, tbl))) + return true; + } + tbl->db= empty_c_string; + tbl->db_length= 0; + tbl->schema_table= 0; + if (tbl->derived) + { + tbl->derived->first_select()->linkage= DERIVED_TABLE_TYPE; + } + if (tbl->with->is_recursive && tbl->is_with_table_recursive_reference()) + continue; + with_elem->inc_references(); + } + return false; +} + + +/** + @brief + Find out dependencies between CTEs, resolve references to them + + @details + The function can be called in two modes. With this->with_cte_resolution + set to false the function only finds out all dependencies between CTEs + used in a query expression with a WITH clause whose parsing has been + just finished. Based on these dependencies recursive CTEs are detected. + If this->with_cte_resolution is set to true the function additionally + resolves all references to CTE occurred in this query expression. + + @retval + true on failure + false on success +*/ + +bool +LEX::check_cte_dependencies_and_resolve_references() +{ + if (check_dependencies_in_with_clauses()) + return true; + if (!with_cte_resolution) + return false; + if (resolve_references_to_cte(query_tables, query_tables_last)) + return true; + if (resolve_references_to_cte_in_hanging_cte()) + return true; + return false; +} + + /** @brief Check dependencies between tables defined in this with clause @@ -112,10 +306,11 @@ bool With_clause::check_dependencies() elem != with_elem; elem= elem->next) { - if (my_strcasecmp(system_charset_info, with_elem->query_name->str, - elem->query_name->str) == 0) + if (my_strcasecmp(system_charset_info, with_elem->get_name_str(), + elem->get_name_str()) == 0) { - my_error(ER_DUP_QUERY_NAME, MYF(0), with_elem->query_name->str); + my_error(ER_DUP_QUERY_NAME, MYF(0), + with_elem->get_name_str()); return true; } } @@ -222,12 +417,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table, with_elem != barrier; with_elem= with_elem->next) { - if (my_strcasecmp(system_charset_info, with_elem->query_name->str, - table->table_name) == 0 && + if (my_strcasecmp(system_charset_info, with_elem->get_name_str(), + table->table_name) == 0 && !table->is_fqtn) { table->set_derived(); - table->db= empty_c_string; + with_elem->referenced= true; return with_elem; } } @@ -584,7 +779,7 @@ bool With_clause::check_anchors() if (elem == with_elem) { my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -617,7 +812,7 @@ bool With_clause::check_anchors() if (elem->work_dep_map & elem->get_elem_map()) { my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -771,7 +966,8 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end, @brief Create a clone of the specification for the given with table - @param thd The context of the statement containing this with element + @param old_lex The LEX structure created for the query or CTE specification + where this With_element is defined @param with_table The reference to the table defined in this element for which the clone is created. @@ -781,12 +977,13 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end, this element. The clone is created when the string with the specification saved in unparsed_spec is fed into the parser as an input string. The parsing - this string a unit object representing the specification is build. + this string a unit object representing the specification is built. A chain of all table references occurred in the specification is also formed. The method includes the new unit and its sub-unit into hierarchy of the units of the main query. I also insert the constructed chain of the table references into the chain of all table references of the main query. + The method resolves all references to CTE in the clone. @note Clones is created only for not first references to tables defined in @@ -802,115 +999,129 @@ bool With_element::set_unparsed_spec(THD *thd, char *spec_start, char *spec_end, NULL - otherwise */ -st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, +st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table) { + THD *thd= old_lex->thd; LEX *lex; - st_select_lex_unit *res= NULL; - Query_arena backup; - Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup); + st_select_lex_unit *res= NULL; if (!(lex= (LEX*) new(thd->mem_root) st_lex_local)) - { - if (arena) - thd->restore_active_arena(arena, &backup); return res; - } - LEX *old_lex= thd->lex; thd->lex= lex; bool parse_status= false; - Parser_state parser_state; - TABLE_LIST *spec_tables; - TABLE_LIST *spec_tables_tail; st_select_lex *with_select; char save_end= unparsed_spec.str[unparsed_spec.length]; unparsed_spec.str[unparsed_spec.length]= '\0'; - if (parser_state.init(thd, unparsed_spec.str, unparsed_spec.length)) - goto err; - parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; - parser_state.m_lip.multi_statements= false; - parser_state.m_lip.m_digest= NULL; lex_start(thd); lex->clone_spec_offset= unparsed_spec_offset; - lex->param_list= old_lex->param_list; - lex->sphead= old_lex->sphead; - lex->spname= old_lex->spname; - lex->spcont= old_lex->spcont; - lex->sp_chistics= old_lex->sp_chistics; - - lex->stmt_lex= old_lex; - with_select= &lex->select_lex; - with_select->select_number= ++thd->lex->stmt_lex->current_select_number; - parse_status= parse_sql(thd, &parser_state, 0); + lex->with_cte_resolution= true; + + /* + The specification of a CTE is to be parsed as a regular query. + At the very end of the parsing query the function + check_cte_dependencies_and_resolve_references() will be called. + It will check the dependencies between CTEs that are defined + within the query and will resolve CTE references in this query. + If a table reference is not resolved as a CTE reference within + this query it still can be resolved as a reference to a CTE defined + in the same clause as the CTE whose specification is to be parsed + or defined in an embedding CTE definition. + + Example: + with + cte1 as ( ... ), + cte2 as ([WITH ...] select ... from cte1 ...) + select ... from cte2 as r, ..., cte2 as s ... + + Here the specification of cte2 has be cloned for table reference + with alias s1. The specification contains a reference to cte1 + that is defined outside this specification. If the reference to + cte1 cannot be resolved within the specification of cte2 it's + not necessarily has to be a reference to a non-CTE table. That's + why the flag lex->only_cte_resolution has to be set to true + before parsing of the specification of cte2 invoked by this + function starts. Otherwise an mdl_lock would be requested for s + and this would not be correct. + */ + + lex->only_cte_resolution= true; + + lex->stmt_lex= old_lex->stmt_lex ? old_lex->stmt_lex : old_lex; + + parse_status= thd->sql_parser(old_lex, lex, + (char*) unparsed_spec.str, + (unsigned int)unparsed_spec.length, + stmt_prepare_mode); + unparsed_spec.str[unparsed_spec.length]= save_end; + with_select= lex->unit.first_select(); + with_select->select_number= ++lex->stmt_lex->current_select_number; if (parse_status) goto err; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - goto err; - - spec_tables= lex->query_tables; - spec_tables_tail= 0; - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) - { - if (!tbl->derived && !tbl->schema_table && - thd->open_temporary_table(tbl)) - goto err; - spec_tables_tail= tbl; - } - if (spec_tables) + /* + The global chain of TABLE_LIST objects created for the specification that + just has been parsed is added to such chain that contains the reference + to the CTE whose specification is parsed right after the TABLE_LIST object + created for the reference. + */ + if (lex->query_tables) { - if (with_table->next_global) + head->tables_pos.set_start_pos(&with_table->next_global); + head->tables_pos.set_end_pos(lex->query_tables_last); + TABLE_LIST *next_tbl= with_table->next_global; + if (next_tbl) { - spec_tables_tail->next_global= with_table->next_global; - with_table->next_global->prev_global= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= next_tbl->prev_global)= + lex->query_tables; + *(next_tbl->prev_global= lex->query_tables_last)= next_tbl; } else { - old_lex->query_tables_last= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= old_lex->query_tables_last)= + lex->query_tables; + old_lex->query_tables_last= lex->query_tables_last; } - spec_tables->prev_global= &with_table->next_global; - with_table->next_global= spec_tables; } res= &lex->unit; res->with_element= this; + /* + The unit of the specification that just has been parsed is included + as a slave of the select that contained in its from list the table + reference for which the unit has been created. + */ lex->unit.include_down(with_table->select_lex); - lex->unit.set_slave(with_select); + lex->unit.set_slave(with_select); + lex->unit.cloned_from= spec; old_lex->all_selects_list= (st_select_lex*) (lex->all_selects_list-> insert_chain_before( (st_select_lex_node **) &(old_lex->all_selects_list), with_select)); - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - res= NULL; + /* - Resolve references to CTE from the spec_tables list that has not - been resolved yet. + Now all references to the CTE defined outside of the cloned specification + has to be resolved. Additionally if old_lex->only_cte_resolution == false + for the table references that has not been resolved requests for mdl_locks + has to be set. */ - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) + lex->only_cte_resolution= old_lex->only_cte_resolution; + if (lex->resolve_references_to_cte(lex->query_tables, + lex->query_tables_last)) { - if (!tbl->with) - tbl->with= with_select->find_table_def_in_with_clauses(tbl); - if (tbl == spec_tables_tail) - break; - } - if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) + res= NULL; goto err; + } - lex->sphead= NULL; // in order not to delete lex->sphead + lex->sphead= NULL; // in order not to delete lex->sphead lex_end(lex); err: - if (arena) - thd->restore_active_arena(arena, &backup); thd->lex= old_lex; return res; } @@ -1080,58 +1291,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table) } -/** - @brief - Set the specifying unit in this reference to a with table - - @details - The method assumes that the given element with_elem defines the table T - this table reference refers to. - If this is the first reference to T the method just sets its specification - in the field 'derived' as the unit that yields T. Otherwise the method - first creates a clone specification and sets rather this clone in this field. - - @retval - false on success - true on failure -*/ - -bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem) -{ - if (table) - { - /* - This table was prematurely identified as a temporary table. - We correct it here, but it's not a nice solution in the case - when the temporary table with this name is not used anywhere - else in the query. - */ - thd->mark_tmp_table_as_free_for_reuse(table); - table= 0; - } - with= with_elem; - schema_table= NULL; - if (!with_elem->is_referenced() || with_elem->is_recursive) - { - derived= with_elem->spec; - if (derived != select_lex->master_unit() && - !with_elem->is_recursive && - !is_with_table_recursive_reference()) - { - derived->move_as_slave(select_lex); - } - } - else - { - if(!(derived= with_elem->clone_parsed_spec(thd, this))) - return true; - } - derived->first_select()->linkage= DERIVED_TABLE_TYPE; - with_elem->inc_references(); - return false; -} - - bool TABLE_LIST::is_recursive_with_table() { return with && with->is_recursive; @@ -1231,7 +1390,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant) if (only_standard_compliant && with_elem->is_unrestricted()) { my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE, - MYF(0), with_elem->query_name->str); + MYF(0), with_elem->get_name_str()); return true; } @@ -1372,7 +1531,7 @@ bool st_select_lex::check_subqueries_with_recursive_references() sl_master->derived && sl_master->derived->is_materialized_derived()) { my_error(ER_REF_TO_RECURSIVE_WITH_TABLE_IN_DERIVED, - MYF(0), with_elem->query_name->str); + MYF(0), with_elem->get_name_str()); return true; } if (!sl_master->item) @@ -1434,7 +1593,23 @@ void With_clause::print(String *str, enum_query_type query_type) void With_element::print(String *str, enum_query_type query_type) { - str->append(query_name); + str->append(get_name()); + if (column_list.elements) + { + List_iterator_fast li(column_list); + str->append('('); + for (LEX_STRING *col_name= li++; ; ) + { + str->append(col_name); + col_name= li++; + if (!col_name) + { + str->append(')'); + break; + } + str->append(','); + } + } str->append(STRING_WITH_LEN(" as ")); str->append('('); spec->print(str, query_type); diff --git a/sql/sql_cte.h b/sql/sql_cte.h index 58f371d936b..5f30894afb1 100644 --- a/sql/sql_cte.h +++ b/sql/sql_cte.h @@ -8,6 +8,39 @@ class select_union; struct st_unit_ctxt_elem; +/** + @class With_element_head + @brief Head of the definition of a CTE table + + It contains the name of the CTE and it contains the position of the subchain + of table references used in the definition in the global chain of table + references used in the query where this definition is encountered. +*/ + +class With_element_head : public Sql_alloc +{ + /* The name of the defined CTE */ + LEX_CSTRING *query_name; + +public: + /* + The structure describing the subchain of the table references used in + the specification of the defined CTE in the global chain of table + references used in the query. The structure is fully defined only + after the CTE definition has been parsed. + */ + TABLE_CHAIN tables_pos; + + With_element_head(LEX_CSTRING *name) + : query_name(name) + { + tables_pos.set_start_pos(0); + tables_pos.set_end_pos(0); + } + friend class With_element; +}; + + /** @class With_element @brief Definition of a CTE table @@ -69,9 +102,22 @@ private: subqueries and specifications of other with elements). */ uint references; + + /* + true <=> this With_element is referred in the query in which the + element is defined + */ + bool referenced; + + /* + true <=> this With_element is needed for the execution of the query + in which the element is defined + */ + bool is_used_in_query; + /* Unparsed specification of the query that specifies this element. - It used to build clones of the specification if they are needed. + It's used to build clones of the specification if they are needed. */ LEX_STRING unparsed_spec; /* Offset of the specification in the input string */ @@ -85,10 +131,11 @@ private: public: /* - The name of the table introduced by this with elememt. The name - can be used in FROM lists of the queries in the scope of the element. + Contains the name of the defined With element and the position of + the subchain of the tables references used by its definition in the + global chain of TABLE_LIST objects created for the whole query. */ - LEX_STRING *query_name; + With_element_head *head; /* Optional list of column names to name the columns of the table introduced by this with element. It is used in the case when the names are not @@ -144,18 +191,27 @@ public: /* List of Item_subselects containing recursive references to this CTE */ SQL_I_List sq_with_rec_ref; - With_element(LEX_STRING *name, + With_element(With_element_head *h, List list, st_select_lex_unit *unit) : next(NULL), base_dep_map(0), derived_dep_map(0), sq_dep_map(0), work_dep_map(0), mutually_recursive(0), top_level_dep_map(0), sq_rec_ref(NULL), next_mutually_recursive(NULL), references(0), - query_name(name), column_list(list), spec(unit), + referenced(false), is_used_in_query(false), + head(h), column_list(list), spec(unit), is_recursive(false), rec_outer_references(0), with_anchor(false), level(0), rec_result(NULL) { unit->with_element= this; } + LEX_CSTRING *get_name() { return head->query_name; } + const char *get_name_str() { return get_name()->str; } + + void set_tables_start_pos(TABLE_LIST **pos) + { head->tables_pos.set_start_pos(pos); } + void set_tables_end_pos(TABLE_LIST **pos) + { head->tables_pos.set_end_pos(pos); } + bool check_dependencies_in_spec(); void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt, @@ -182,9 +238,9 @@ public: bool set_unparsed_spec(THD *thd, char *spec_start, char *spec_end, uint spec_offset); - st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table); + st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table); - bool is_referenced() { return references != 0; } + bool is_referenced() { return referenced; } void inc_references() { references++; } @@ -242,6 +298,12 @@ public: void prepare_for_next_iteration(); friend class With_clause; + + friend + bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8; @@ -338,8 +400,10 @@ public: friend class With_element; friend - bool - check_dependencies_in_with_clauses(With_clause *with_clauses_list); + bool LEX::check_dependencies_in_with_clauses(); + + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; inline diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index c534ba76670..eaaa3139c59 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -704,6 +704,8 @@ void lex_start(THD *thd) lex->subqueries= FALSE; lex->context_analysis_only= 0; lex->derived_tables= 0; + lex->with_cte_resolution= false; + lex->only_cte_resolution= false; lex->safe_to_cache_query= 1; lex->parsing_options.reset(); lex->empty_field_list_on_rset= 0; @@ -2089,6 +2091,7 @@ void st_select_lex_unit::init_query() is_view= false; with_clause= 0; with_element= 0; + cloned_from= 0; columns_are_renamed= false; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index fbc2bf7f822..03c06b9ae0f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -666,6 +666,8 @@ public: With_clause *with_clause; /* With element where this unit is used as the specification (if any) */ With_element *with_element; + /* The unit used as a CTE specification from which this unit is cloned */ + st_select_lex_unit *cloned_from; /* thread handler */ THD *thd; /* @@ -1169,7 +1171,9 @@ public: } With_element *get_with_element() { - return master_unit()->with_element; + return master_unit()->cloned_from ? + master_unit()->cloned_from->with_element : + master_unit()->with_element; } With_element *find_table_def_in_with_clauses(TABLE_LIST *table); bool check_unrestricted_recursive(bool only_standard_compliant); @@ -2772,6 +2776,20 @@ public: uint16 create_view_algorithm; uint8 create_view_check; uint8 context_analysis_only; + /* + true <=> The parsed fragment requires resolution of references to CTE + at the end of parsing. This name resolution process involves searching + for possible dependencies between CTE defined in the parsed fragment and + detecting possible recursive references. + The flag is set to true if the fragment contains CTE definitions. + */ + bool with_cte_resolution; + /* + true <=> only resolution of references to CTE are required in the parsed + fragment, no checking of dependencies between CTE is required. + This flag is used only when parsing clones of CTE specifications. + */ + bool only_cte_resolution; bool local_file; bool check_exists; bool autocommit; @@ -3224,6 +3242,13 @@ public: { return !create_like() && !create_select(); } + + bool check_dependencies_in_with_clauses(); + bool resolve_references_to_cte_in_hanging_cte(); + bool check_cte_dependencies_and_resolve_references(); + bool resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); + }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9436e111043..f782c5c25e7 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3220,9 +3220,6 @@ mysql_execute_command(THD *thd) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); } - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - DBUG_RETURN(1); - #ifdef HAVE_REPLICATION if (unlikely(thd->slave_thread)) { @@ -7980,7 +7977,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->db= table->db.str; ptr->db_length= table->db.length; } - else if (lex->copy_db_to(&ptr->db, &ptr->db_length)) + else if (!lex->with_cte_resolution && + lex->copy_db_to(&ptr->db, &ptr->db_length)) DBUG_RETURN(0); else ptr->is_fqtn= FALSE; @@ -7997,7 +7995,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->table_name=table->table.str; ptr->table_name_length=table->table.length; - ptr->lock_type= lock_type; + ptr->lock_type= lock_type; + ptr->mdl_type= mdl_type; + ptr->table_options= table_options; ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING); /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */ ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX); @@ -8676,8 +8676,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update { tables->lock_type= lock_type; tables->updating= for_update; - tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + + if (tables->db && tables->db[0]) + tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } DBUG_VOID_RETURN; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index a8a671075f9..922d7c92796 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2413,9 +2413,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) if (tables) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto error; - if (sql_command_flags[sql_command] & CF_HA_CLOSE) mysql_ha_rm_tables(thd, tables); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 0547a2fe856..39744fe5704 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -432,12 +432,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_type= OT_BASE_ONLY; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - { - res= TRUE; - goto err; - } - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* @@ -1406,9 +1400,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, TABLE_LIST *tbl; Security_context *security_ctx= 0; - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto err; - /* Check rights to run commands (ANALYZE SELECT, EXPLAIN SELECT & SHOW CREATE) which show underlying tables. diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 387c77a4ef5..25826d2d6b0 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -981,6 +981,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) class sp_label *splabel; class sp_name *spname; class sp_variable *spvar; + class With_element_head *with_element_head; class With_clause *with_clause; class Virtual_column_info *virtual_column; @@ -2067,7 +2068,7 @@ END_OF_INPUT %type opt_with_clause with_clause -%type query_name +%type with_element_head %type opt_with_column_list @@ -2959,7 +2960,11 @@ call: lex->value_list.empty(); sp_add_used_routine(lex, thd, $2, TYPE_ENUM_PROCEDURE); } - opt_sp_cparam_list {} + opt_sp_cparam_list + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; /* CALL parameters */ @@ -3805,6 +3810,8 @@ sp_proc_stmt_return: { LEX *lex= Lex; sp_head *sp= lex->sphead; + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; if (sp->m_type != TYPE_ENUM_FUNCTION) my_yyabort_error((ER_SP_BADRETURN, MYF(0))); @@ -4866,12 +4873,16 @@ create_select_query_expression: { Select->set_braces(0); Select->set_with_clause($1); + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } union_clause | opt_with_clause SELECT_SYM create_select_part2 create_select_part3_union_not_ready create_select_part4 { Select->set_with_clause($1); + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } | '(' create_select_query_specification ')' | '(' create_select_query_specification ')' @@ -5578,6 +5589,8 @@ create_select_query_specification: create_select_part4 { Select->set_with_clause($1); + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -8454,6 +8467,8 @@ select: LEX *lex= Lex; lex->sql_command= SQLCOM_SELECT; lex->current_select->set_with_clause($1); + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -12161,6 +12176,8 @@ do: expr_list { Lex->insert_list= $3; + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -12377,7 +12394,10 @@ insert: Lex->current_select= &Lex->select_lex; } insert_field_spec opt_insert_update - {} + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; replace: @@ -12394,7 +12414,10 @@ replace: Lex->current_select= &Lex->select_lex; } insert_field_spec - {} + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; insert_lock_option: @@ -12586,7 +12609,11 @@ update: */ slex->set_lock_for_tables($3, slex->table_list.elements == 1); } - opt_where_clause opt_order_clause delete_limit_clause {} + opt_where_clause opt_order_clause delete_limit_clause + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; update_list: @@ -12675,6 +12702,8 @@ single_multi: { if (multi_delete_set_locks_and_link_aux_tables(Lex)) MYSQL_YYABORT; + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -13641,7 +13670,10 @@ load: opt_xml_rows_identified_by opt_field_term opt_line_term opt_ignore_lines opt_field_or_var_spec opt_load_data_set_spec - {} + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; data_or_xml: @@ -14096,6 +14128,8 @@ with_clause: if (with_clause == NULL) MYSQL_YYABORT; Lex->derived_tables|= DERIVED_WITH; + Lex->with_cte_resolution= true; + Lex->with_cte_resolution= true; Lex->curr_with_clause= with_clause; with_clause->add_to_list(Lex->with_clauses_list_last_next); } @@ -14120,7 +14154,7 @@ with_list: with_list_element: - query_name + with_element_head opt_with_column_list { $2= new List (Lex->with_column_list); @@ -14140,6 +14174,7 @@ with_list_element: if (elem->set_unparsed_spec(thd, spec_start, $8, (uint) (spec_start - query_start))) MYSQL_YYABORT; + elem->set_tables_end_pos(lex->query_tables_last); } ; @@ -14166,12 +14201,15 @@ with_column_list: ; -query_name: +with_element_head: ident { - $$= (LEX_STRING *) thd->memdup(&$1, sizeof(LEX_STRING)); - if ($$ == NULL) + LEX_CSTRING *name= + (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); + $$= new (thd->mem_root) With_element_head(name); + if (unlikely(name == NULL || $$ == NULL)) MYSQL_YYABORT; + $$->tables_pos.set_start_pos(Lex->query_tables_last); } ; @@ -15078,7 +15116,10 @@ set: sp_create_assignment_lex(thd, yychar == YYEMPTY); } start_option_value_list - {} + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } | SET STATEMENT_SYM { LEX *lex= Lex; @@ -16691,6 +16732,9 @@ view_select: ¬_used); lex->parsing_options.allows_variable= TRUE; lex->current_select->set_with_clause($2); + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; diff --git a/sql/table.h b/sql/table.h index 83c72f76831..69bd14b2834 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1860,6 +1860,29 @@ class Item_in_subselect; struct LEX; class Index_hint; + +/* + @struct TABLE_CHAIN + @brief Subchain of global chain of table references + + The structure contains a pointer to the address of the next_global + pointer to the first TABLE_LIST objectof the subchain and the address + of the next_global pointer to the element right after the last + TABLE_LIST object of the subchain. For an empty subchain both pointers + have the same value. +*/ + +struct TABLE_CHAIN +{ + TABLE_CHAIN() {} + + TABLE_LIST **start_pos; + TABLE_LIST ** end_pos; + + void set_start_pos(TABLE_LIST **pos) { start_pos= pos; } + void set_end_pos(TABLE_LIST **pos) { end_pos= pos; } +}; + struct TABLE_LIST { TABLE_LIST() {} /* Remove gcc warning */ @@ -2166,6 +2189,20 @@ struct TABLE_LIST /* call back function for asking handler about caching in query cache */ qc_engine_callback callback_func; thr_lock_type lock_type; + + /* + Two fields below are set during parsing this table reference in the cases + when the table reference can be potentially a reference to a CTE table. + In this cases the fact that the reference is a reference to a CTE or not + will be ascertained at the very end of parsing of the query when referencies + to CTE are resolved. For references to CTE and to derived tables no mdl + requests are needed while for other table references they are. If a request + is possibly postponed the info that allows to issue this request must be + saved in 'mdl_type' and 'table_options'. + */ + enum_mdl_type mdl_type; + ulong table_options; + uint outer_join; /* Which join type */ uint shared; /* Used in multi-upd */ size_t db_length; -- cgit v1.2.1 From 67083ca4f3dd11f44810e22d370e6c3b01e3bc54 Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Sat, 22 May 2021 01:17:46 +0200 Subject: MDEV-25719 post-merge correction: wsrep_debug=ON -> wsrep_debug=1 --- mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result | 2 ++ mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result index 684a6d00c7e..8e7d19ce3a2 100644 --- a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -1,3 +1,5 @@ +connection node_2; +connection node_1; SELECT 1; 1 1 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf index 25a9d5fc0b2..0dba13ecffd 100644 --- a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -3,7 +3,7 @@ [mysqld] wsrep_sst_method=mariabackup wsrep_sst_auth="root:" -wsrep_debug=ON +wsrep_debug=1 [sst] transferfmt=@ENV.MTR_GALERA_TFMT -- cgit v1.2.1 From 2c90dc091c9ff4af88d4ac71670f0e720dc1ba5d Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Sat, 22 May 2021 02:16:38 +0200 Subject: MDEV-25719 post-merge correction: wsrep_debug=ON -> wsrep_debug=1 --- mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result | 2 ++ mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result index 684a6d00c7e..8e7d19ce3a2 100644 --- a/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result +++ b/mysql-test/suite/galera/r/galera_sst_mariabackup_qpress.result @@ -1,3 +1,5 @@ +connection node_2; +connection node_1; SELECT 1; 1 1 diff --git a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf index 25a9d5fc0b2..0dba13ecffd 100644 --- a/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf +++ b/mysql-test/suite/galera/t/galera_sst_mariabackup_qpress.cnf @@ -3,7 +3,7 @@ [mysqld] wsrep_sst_method=mariabackup wsrep_sst_auth="root:" -wsrep_debug=ON +wsrep_debug=1 [sst] transferfmt=@ENV.MTR_GALERA_TFMT -- cgit v1.2.1 From 681918a849343b0d247968dc0825dc49f2f1fb09 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 13 May 2021 18:35:02 +0200 Subject: MDEV-24996 file conflict in rpm packages --- cmake/cpack_rpm.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/cpack_rpm.cmake b/cmake/cpack_rpm.cmake index 827b1e166a4..1eded9cb48e 100644 --- a/cmake/cpack_rpm.cmake +++ b/cmake/cpack_rpm.cmake @@ -120,8 +120,8 @@ SET(ignored "%ignore ${CMAKE_INSTALL_PREFIX}/share/aclocal" "%ignore ${CMAKE_INSTALL_PREFIX}/share/doc" "%ignore ${CMAKE_INSTALL_PREFIX}/share/man" - "%ignore ${CMAKE_INSTALL_PREFIX}/share/man/man1*" - "%ignore ${CMAKE_INSTALL_PREFIX}/share/man/man8*" + "%ignore ${CMAKE_INSTALL_PREFIX}/share/man/man1" + "%ignore ${CMAKE_INSTALL_PREFIX}/share/man/man8" "%ignore ${CMAKE_INSTALL_PREFIX}/share/pkgconfig" ) -- cgit v1.2.1 From 6bf866cc79230cfc3b0305cd8b3cfa187ea01ecd Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 14 May 2021 14:45:53 +0200 Subject: MDEV-25641 max_password_errors not working with ed25519 auth plugin report correct error codes in ed25519. Invalid value stored in the user table or an OpenSSL error is CR_ERROR. When a user provided incorrect password when logging in - it's CR_AUTH_USER_CREDENTIALS. --- .../r/hostcache_ipv4_auth_ed25519.result | 119 +++++++++++++++++++++ .../perfschema/t/hostcache_ipv4_auth_ed25519.test | 53 +++++++++ plugin/auth_ed25519/server_ed25519.c | 8 +- 3 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 mysql-test/suite/perfschema/r/hostcache_ipv4_auth_ed25519.result create mode 100644 mysql-test/suite/perfschema/t/hostcache_ipv4_auth_ed25519.test diff --git a/mysql-test/suite/perfschema/r/hostcache_ipv4_auth_ed25519.result b/mysql-test/suite/perfschema/r/hostcache_ipv4_auth_ed25519.result new file mode 100644 index 00000000000..ac40e37dd66 --- /dev/null +++ b/mysql-test/suite/perfschema/r/hostcache_ipv4_auth_ed25519.result @@ -0,0 +1,119 @@ +install soname 'auth_ed25519'; +flush status; +flush hosts; +flush user_resources; +flush privileges; +select `User`, `Host` from mysql.`user` where `host` like '%\\%%'; +User Host +select `User`, `Host` from mysql.`user` where `user` like '192.%'; +User Host +select `User`, `Host` from mysql.`user` where `user` like '2001:%'; +User Host +select `User`, `Host` from mysql.`user` where `user` like 'santa.claus.%'; +User Host +create user plug1@'santa.claus.ipv4.example.com' + identified with ed25519 as 'foo'; +create user plug2@'santa.claus.ipv4.example.com' + identified with ED25519 as 'vubFBzIrapbfHct1/J72dnUryz5VS7lA6XHH8sIx4TI'; +set @saved_dbug = @@global.debug_dbug; +set global debug_dbug= "+d,vio_peer_addr_fake_ipv4,getnameinfo_fake_ipv4,getaddrinfo_fake_good_ipv4"; +connect(127.0.0.1,plug1,foo,test,PORT,SOCKET); +connect con1, 127.0.0.1, plug1,foo,,$MASTER_MYPORT; +ERROR 28000: Access denied for user 'plug1'@'santa.claus.ipv4.example.com' (using password: NO) +"Dumping performance_schema.host_cache" +IP 192.0.2.4 +HOST santa.claus.ipv4.example.com +HOST_VALIDATED YES +SUM_CONNECT_ERRORS 0 +COUNT_HOST_BLOCKED_ERRORS 0 +COUNT_NAMEINFO_TRANSIENT_ERRORS 0 +COUNT_NAMEINFO_PERMANENT_ERRORS 0 +COUNT_FORMAT_ERRORS 0 +COUNT_ADDRINFO_TRANSIENT_ERRORS 0 +COUNT_ADDRINFO_PERMANENT_ERRORS 0 +COUNT_FCRDNS_ERRORS 0 +COUNT_HOST_ACL_ERRORS 0 +COUNT_NO_AUTH_PLUGIN_ERRORS 0 +COUNT_AUTH_PLUGIN_ERRORS 1 +COUNT_HANDSHAKE_ERRORS 0 +COUNT_PROXY_USER_ERRORS 0 +COUNT_PROXY_USER_ACL_ERRORS 0 +COUNT_AUTHENTICATION_ERRORS 0 +COUNT_SSL_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS 0 +COUNT_DEFAULT_DATABASE_ERRORS 0 +COUNT_INIT_CONNECT_ERRORS 0 +COUNT_LOCAL_ERRORS 0 +COUNT_UNKNOWN_ERRORS 0 +FIRST_ERROR_SEEN set +LAST_ERROR_SEEN set +connect(127.0.0.1,plug2,bar,test,PORT,SOCKET); +connect con1, 127.0.0.1, plug2,bar,,$MASTER_MYPORT; +ERROR 28000: Access denied for user 'plug2'@'santa.claus.ipv4.example.com' (using password: YES) +"Dumping performance_schema.host_cache" +IP 192.0.2.4 +HOST santa.claus.ipv4.example.com +HOST_VALIDATED YES +SUM_CONNECT_ERRORS 0 +COUNT_HOST_BLOCKED_ERRORS 0 +COUNT_NAMEINFO_TRANSIENT_ERRORS 0 +COUNT_NAMEINFO_PERMANENT_ERRORS 0 +COUNT_FORMAT_ERRORS 0 +COUNT_ADDRINFO_TRANSIENT_ERRORS 0 +COUNT_ADDRINFO_PERMANENT_ERRORS 0 +COUNT_FCRDNS_ERRORS 0 +COUNT_HOST_ACL_ERRORS 0 +COUNT_NO_AUTH_PLUGIN_ERRORS 0 +COUNT_AUTH_PLUGIN_ERRORS 1 +COUNT_HANDSHAKE_ERRORS 0 +COUNT_PROXY_USER_ERRORS 0 +COUNT_PROXY_USER_ACL_ERRORS 0 +COUNT_AUTHENTICATION_ERRORS 1 +COUNT_SSL_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS 0 +COUNT_DEFAULT_DATABASE_ERRORS 0 +COUNT_INIT_CONNECT_ERRORS 0 +COUNT_LOCAL_ERRORS 0 +COUNT_UNKNOWN_ERRORS 0 +FIRST_ERROR_SEEN set +LAST_ERROR_SEEN set +connect con1, 127.0.0.1, plug2,foo,,$MASTER_MYPORT; +select current_user(); +current_user() +plug2@santa.claus.ipv4.example.com +disconnect con1; +connection default; +"Dumping performance_schema.host_cache" +IP 192.0.2.4 +HOST santa.claus.ipv4.example.com +HOST_VALIDATED YES +SUM_CONNECT_ERRORS 0 +COUNT_HOST_BLOCKED_ERRORS 0 +COUNT_NAMEINFO_TRANSIENT_ERRORS 0 +COUNT_NAMEINFO_PERMANENT_ERRORS 0 +COUNT_FORMAT_ERRORS 0 +COUNT_ADDRINFO_TRANSIENT_ERRORS 0 +COUNT_ADDRINFO_PERMANENT_ERRORS 0 +COUNT_FCRDNS_ERRORS 0 +COUNT_HOST_ACL_ERRORS 0 +COUNT_NO_AUTH_PLUGIN_ERRORS 0 +COUNT_AUTH_PLUGIN_ERRORS 1 +COUNT_HANDSHAKE_ERRORS 0 +COUNT_PROXY_USER_ERRORS 0 +COUNT_PROXY_USER_ACL_ERRORS 0 +COUNT_AUTHENTICATION_ERRORS 1 +COUNT_SSL_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_ERRORS 0 +COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS 0 +COUNT_DEFAULT_DATABASE_ERRORS 0 +COUNT_INIT_CONNECT_ERRORS 0 +COUNT_LOCAL_ERRORS 0 +COUNT_UNKNOWN_ERRORS 0 +FIRST_ERROR_SEEN set +LAST_ERROR_SEEN set +drop user plug1@'santa.claus.ipv4.example.com'; +drop user plug2@'santa.claus.ipv4.example.com'; +set @@global.debug_dbug = @saved_dbug; +uninstall plugin ed25519; diff --git a/mysql-test/suite/perfschema/t/hostcache_ipv4_auth_ed25519.test b/mysql-test/suite/perfschema/t/hostcache_ipv4_auth_ed25519.test new file mode 100644 index 00000000000..6a97b6d0958 --- /dev/null +++ b/mysql-test/suite/perfschema/t/hostcache_ipv4_auth_ed25519.test @@ -0,0 +1,53 @@ +# +# Tests for the performance_schema host_cache. +# +# Test authorization with auth plugins. +# error reporting in: +# - column COUNT_AUTH_PLUGIN_ERRORS +# - column COUNT_PROXY_USER_ERRORS +# - column COUNT_PROXY_USER_ACL_ERRORS + +source include/not_embedded.inc; +source include/have_debug.inc; +source include/have_perfschema.inc; +source include/have_plugin_auth.inc; +source include/have_hostname_cache.inc; + +if (!$AUTH_ED25519_SO) { + skip No auth_ed25519 plugin; +} +install soname 'auth_ed25519'; + +# Enforce a clean state +source ../include/wait_for_pfs_thread_count.inc; +source ../include/hostcache_set_state.inc; + +create user plug1@'santa.claus.ipv4.example.com' + identified with ed25519 as 'foo'; +create user plug2@'santa.claus.ipv4.example.com' + identified with ED25519 as 'vubFBzIrapbfHct1/J72dnUryz5VS7lA6XHH8sIx4TI'; + +set @saved_dbug = @@global.debug_dbug; +set global debug_dbug= "+d,vio_peer_addr_fake_ipv4,getnameinfo_fake_ipv4,getaddrinfo_fake_good_ipv4"; + +replace_result $MASTER_MYPORT PORT $MASTER_MYSOCK SOCKET; +error ER_ACCESS_DENIED_ERROR; +connect con1, 127.0.0.1, plug1,foo,,$MASTER_MYPORT; +source ../include/hostcache_dump.inc; + +replace_result $MASTER_MYPORT PORT $MASTER_MYSOCK SOCKET; +error ER_ACCESS_DENIED_ERROR; +connect con1, 127.0.0.1, plug2,bar,,$MASTER_MYPORT; +source ../include/hostcache_dump.inc; + +connect con1, 127.0.0.1, plug2,foo,,$MASTER_MYPORT; +select current_user(); +disconnect con1; +connection default; +source ../include/hostcache_dump.inc; + +drop user plug1@'santa.claus.ipv4.example.com'; +drop user plug2@'santa.claus.ipv4.example.com'; + +set @@global.debug_dbug = @saved_dbug; +uninstall plugin ed25519; diff --git a/plugin/auth_ed25519/server_ed25519.c b/plugin/auth_ed25519/server_ed25519.c index 81fc3e66755..e9678450042 100644 --- a/plugin/auth_ed25519/server_ed25519.c +++ b/plugin/auth_ed25519/server_ed25519.c @@ -41,17 +41,17 @@ static int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) /* prepare the pk */ if (info->auth_string_length != PASSWORD_LEN) - return CR_AUTH_USER_CREDENTIALS; + return CR_ERROR; // bad password in the user table memcpy(pw, info->auth_string, PASSWORD_LEN); pw[PASSWORD_LEN]= '='; if (my_base64_decode(pw, PASSWORD_LEN_BUF, pk, NULL, 0) != CRYPTO_PUBLICKEYBYTES) - return CR_AUTH_USER_CREDENTIALS; + return CR_ERROR; // bad password in the user table info->password_used= PASSWORD_USED_YES; /* prepare random nonce */ if (my_random_bytes((unsigned char *)nonce, (int)sizeof(nonce))) - return CR_AUTH_USER_CREDENTIALS; + return CR_ERROR; // eh? OpenSSL error /* send it */ if (vio->write_packet(vio, reply + CRYPTO_BYTES, NONCE_BYTES)) @@ -63,7 +63,7 @@ static int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) memcpy(reply, pkt, CRYPTO_BYTES); if (crypto_sign_open(reply, CRYPTO_BYTES + NONCE_BYTES, pk)) - return CR_ERROR; + return CR_AUTH_USER_CREDENTIALS; // wrong password provided by the user return CR_OK; } -- cgit v1.2.1 From f9f8cae9fe22c133f8c92e395ff70c42e287a2f7 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 14 May 2021 19:55:53 +0200 Subject: cmake: fix FindJava/FindJNI wrappers for cmake re-runs when cmake is re-run and include(FindJAVA) is skipped, JAVA_FOUND should still be set. Same for JNI. --- cmake/FindJNI.cmake | 5 +++++ cmake/FindJava.cmake | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/cmake/FindJNI.cmake b/cmake/FindJNI.cmake index fb2f4801a70..12305d7c86d 100644 --- a/cmake/FindJNI.cmake +++ b/cmake/FindJNI.cmake @@ -1,4 +1,9 @@ +if(JAVA_AWT_LIBRARY) + set(JNI_FOUND TRUE) + return() +endif() if(DEFINED JAVA_AWT_LIBRARY) + set(JNI_FOUND FALSE) return() endif() diff --git a/cmake/FindJava.cmake b/cmake/FindJava.cmake index 95bbf8682cd..714f56b1f72 100644 --- a/cmake/FindJava.cmake +++ b/cmake/FindJava.cmake @@ -1,4 +1,9 @@ +if(Java_JAVA_EXECUTABLE) + set(JAVA_FOUND TRUE) + return() +endif() if(DEFINED Java_JAVA_EXECUTABLE) + set(JAVA_FOUND FALSE) return() endif() -- cgit v1.2.1 From 9d0fde3ba16d7587d13ba35874f357eb67819e4c Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 22 May 2021 12:21:05 +0200 Subject: cmake: silence repeated git searches too --- cmake/FindGit.cmake | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 cmake/FindGit.cmake diff --git a/cmake/FindGit.cmake b/cmake/FindGit.cmake new file mode 100644 index 00000000000..8178b614a3e --- /dev/null +++ b/cmake/FindGit.cmake @@ -0,0 +1,13 @@ +if(GIT_EXECUTABLE) + set(GIT_FOUND TRUE) + return() +endif() +if(DEFINED GIT_EXECUTABLE) + set(GIT_FOUND FALSE) + return() +endif() + +set(orig_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +include(FindGit) +set(CMAKE_MODULE_PATH ${orig_CMAKE_MODULE_PATH}) -- cgit v1.2.1 From d7321893d8c50071632a102e17a7869da9cb03a5 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 14 May 2021 21:25:46 +0200 Subject: CONNECT: move jar files to /usr/share and include them in DEBs --- debian/mariadb-plugin-connect.install | 4 ++++ sql/mysqld.h | 2 +- storage/connect/CMakeLists.txt | 4 ++-- storage/connect/javaconn.cpp | 14 +++++++------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/debian/mariadb-plugin-connect.install b/debian/mariadb-plugin-connect.install index 8a7aee412df..7b5a5f0633e 100644 --- a/debian/mariadb-plugin-connect.install +++ b/debian/mariadb-plugin-connect.install @@ -1,2 +1,6 @@ etc/mysql/conf.d/connect.cnf usr/lib/mysql/plugin/ha_connect.so +usr/share/mysql/Mongo2.jar +usr/share/mysql/Mongo3.jar +usr/share/mysql/JavaWrappers.jar +usr/share/mysql/JdbcInterface.jar diff --git a/sql/mysqld.h b/sql/mysqld.h index 64e5aef5946..44b0491f138 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -536,7 +536,7 @@ extern ulonglong my_pcre_frame_size; */ extern my_bool opt_large_pages; extern uint opt_large_page_size; -extern char lc_messages_dir[FN_REFLEN]; +extern MYSQL_PLUGIN_IMPORT char lc_messages_dir[FN_REFLEN]; extern char *lc_messages_dir_ptr, *log_error_file_ptr; extern MYSQL_PLUGIN_IMPORT char reg_ext[FN_EXTLEN]; extern MYSQL_PLUGIN_IMPORT uint reg_ext_length; diff --git a/storage/connect/CMakeLists.txt b/storage/connect/CMakeLists.txt index e06b59804fb..e9533e8f3e5 100644 --- a/storage/connect/CMakeLists.txt +++ b/storage/connect/CMakeLists.txt @@ -412,11 +412,11 @@ IF(CONNECT_WITH_JDBC AND JAVA_FOUND AND JNI_FOUND) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/connect/std_data/JavaWrappers.jar ${CMAKE_CURRENT_BINARY_DIR}/JdbcInterface.jar - DESTINATION ${INSTALL_PLUGINDIR} COMPONENT connect-engine) + DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT connect-engine) IF(CONNECT_WITH_MONGO) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/connect/std_data/Mongo2.jar ${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/connect/std_data/Mongo3.jar - DESTINATION ${INSTALL_PLUGINDIR} COMPONENT connect-engine) + DESTINATION ${INSTALL_MYSQLSHAREDIR} COMPONENT connect-engine) ENDIF() ENDIF() diff --git a/storage/connect/javaconn.cpp b/storage/connect/javaconn.cpp index 3737c82a02e..42af80d15c4 100644 --- a/storage/connect/javaconn.cpp +++ b/storage/connect/javaconn.cpp @@ -57,8 +57,8 @@ extern "C" HINSTANCE s_hModule; // Saved module handle extern char *JvmPath; // The connect_jvm_path global variable value extern char *ClassPath; // The connect_class_path global variable value -char *GetPluginDir(void); char *GetJavaWrapper(void); // The connect_java_wrapper variable value +extern MYSQL_PLUGIN_IMPORT char lc_messages_dir[FN_REFLEN]; /***********************************************************************/ /* Static JAVAConn objects. */ @@ -401,23 +401,23 @@ bool JAVAConn::Open(PGLOBAL g) } // endif ClassPath #if 0 - // Java source will be compiled as a jar file installed in the plugin dir + // Java source will be compiled as a jar file installed in the mysql share dir jpop->Append(sep); - jpop->Append(GetPluginDir()); + jpop->Append(lc_messages_dir); jpop->Append("JdbcInterface.jar"); #endif // 0 - // All wrappers are pre-compiled in JavaWrappers.jar in the plugin dir + // All wrappers are pre-compiled in JavaWrappers.jar in the mysql share dir jpop->Append(sep); - jpop->Append(GetPluginDir()); + jpop->Append(lc_messages_dir); jpop->Append("JavaWrappers.jar"); #if defined(MONGO_SUPPORT) jpop->Append(sep); - jpop->Append(GetPluginDir()); + jpop->Append(lc_messages_dir); jpop->Append("Mongo3.jar"); jpop->Append(sep); - jpop->Append(GetPluginDir()); + jpop->Append(lc_messages_dir); jpop->Append("Mongo2.jar"); #endif // MONGO_SUPPORT -- cgit v1.2.1 From f70b11c8c9f0febb281511550df0de269fba191b Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 21 May 2021 10:52:12 +0200 Subject: cmake: fewer Build-Depends in SRPM don't require tar/gtar, git, getconf, groff/nroff, and ruby. --- CMakeLists.txt | 2 ++ cmake/build_configurations/mysql_release.cmake | 6 ++++++ cmake/cpu_info.cmake | 4 ++++ dbug/CMakeLists.txt | 2 +- storage/mroonga/vendor/groonga/CMakeLists.txt | 11 +++++------ 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ce95c44d92..45efa38a9f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,11 +122,13 @@ FOREACH(_base ENDIF() ENDFOREACH() +IF(NOT RPM AND NOT DEB) FOREACH(tool gtar tar git) STRING(TOUPPER ${tool} TOOL) FIND_PROGRAM(${TOOL}_EXECUTABLE ${tool} DOC "path to the executable") MARK_AS_ADVANCED(${TOOL}_EXECUTABLE) ENDFOREACH() +ENDIF() # Following autotools tradition, add preprocessor definitions # specified in environment variable CPPFLAGS diff --git a/cmake/build_configurations/mysql_release.cmake b/cmake/build_configurations/mysql_release.cmake index dadfc78e18b..c9c394b6d03 100644 --- a/cmake/build_configurations/mysql_release.cmake +++ b/cmake/build_configurations/mysql_release.cmake @@ -88,6 +88,12 @@ ENDIF() SET(WITH_INNODB_SNAPPY OFF CACHE STRING "") SET(WITH_NUMA 0 CACHE BOOL "") +SET(CPU_LEVEL1_DCACHE_LINESIZE 0) + +IF(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git) + SET(GIT_EXECUTABLE GIT_EXECUTABLE-NOTFOUND CACHE FILEPATH "") +ENDIF() + IF(WIN32) SET(INSTALL_MYSQLTESTDIR "" CACHE STRING "") SET(INSTALL_SQLBENCHDIR "" CACHE STRING "") diff --git a/cmake/cpu_info.cmake b/cmake/cpu_info.cmake index 1acfad5897f..2abeb5e12c1 100644 --- a/cmake/cpu_info.cmake +++ b/cmake/cpu_info.cmake @@ -15,6 +15,8 @@ # Symbols with information about the CPU. +IF(NOT DEFINED CPU_LEVEL1_DCACHE_LINESIZE) + IF(CMAKE_SYSTEM_NAME MATCHES "Darwin") FIND_PROGRAM(SYSCTL sysctl) MARK_AS_ADVANCED(SYSCTL) @@ -37,3 +39,5 @@ ELSE() ) ENDIF() ENDIF() + +ENDIF() diff --git a/dbug/CMakeLists.txt b/dbug/CMakeLists.txt index 8941ac5fc27..a6573b7444e 100644 --- a/dbug/CMakeLists.txt +++ b/dbug/CMakeLists.txt @@ -29,7 +29,7 @@ IF(NOT CMAKE_CROSSCOMPILING) TARGET_LINK_LIBRARIES(factorial dbug) ENDIF() -IF(NOT WIN32 AND NOT CMAKE_GENERATOR MATCHES Xcode) +IF(NOT WIN32 AND NOT CMAKE_GENERATOR MATCHES Xcode AND NOT RPM AND NOT DEB) FIND_PROGRAM(GROFF groff) FIND_PROGRAM(NROFF nroff) MARK_AS_ADVANCED(GROFF) diff --git a/storage/mroonga/vendor/groonga/CMakeLists.txt b/storage/mroonga/vendor/groonga/CMakeLists.txt index e1f45b7fda9..998225d283c 100644 --- a/storage/mroonga/vendor/groonga/CMakeLists.txt +++ b/storage/mroonga/vendor/groonga/CMakeLists.txt @@ -590,14 +590,13 @@ else() set(GRN_WITH_MESSAGE_PACK FALSE) endif() -find_program(RUBY NAMES - "ruby2.3" "ruby23" - "ruby2.2" "ruby22" - "ruby2.1" "ruby21" - "ruby") - option(GRN_WITH_MRUBY "use mruby" OFF) if(GRN_WITH_MRUBY) + find_program(RUBY NAMES + "ruby2.3" "ruby23" + "ruby2.2" "ruby22" + "ruby2.1" "ruby21" + "ruby") set(MRUBY_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/vendor/mruby-source/include") set(MRUBY_LIBS mruby) -- cgit v1.2.1 From c88e9342f37fff6d7757e4f7c6dcc5a7c44e2217 Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Sun, 23 May 2021 01:11:19 +0200 Subject: MDEV-25759: is_local_ip function can come to incorrect conclusion The is_local_ip function that used in Galera SST scripts now incorrectly identifies ip-addresses falling under the "127.0.0.0/8" netmask as non-local ip, although they certainly belong to the loopback interface. This commit fixes this flaw. --- scripts/wsrep_sst_common.sh | 39 ++++++++++++++++++++++++++++++--------- scripts/wsrep_sst_rsync.sh | 26 +++++++++++++++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index f4ac2e9936d..952a37f75d2 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -875,7 +875,9 @@ get_openssl() readonly OPENSSL_BINARY } +# # Generate a string equivalent to 16 random bytes +# wsrep_gen_secret() { get_openssl @@ -889,16 +891,36 @@ wsrep_gen_secret() fi } +# +# Checking if the address passed to us is local. +# If the second parameter is nonzero, then this function +# does not check for matches with local domain names: +# is_local_ip() { - [ "$1" = '127.0.0.1' ] && return 0 - [ "$1" = '127.0.0.2' ] && return 0 - [ "$1" = 'localhost' ] && return 0 - [ "$1" = '[::1]' ] && return 0 - [ "$1" = "$(hostname -s)" ] && return 0 - [ "$1" = "$(hostname -f)" ] && return 0 - [ "$1" = "$(hostname -d)" ] && return 0 - + # Rapid recognition of the most common cases: + [ "$1" = '127.0.0.1' -o \ + "$1" = '127.0.0.2' -o \ + "$1" = 'localhost' -o \ + "$1" = '[::1]' ] && return 0 + # If the address starts with "127." this is probably a local + # address, but we need to clarify what follows this prefix: + if [ "${1#127.}" != "$1" ]; then + # All 127.0.0.0/8 addresses are local: + if echo "$1" | grep -q -E '^127\.[0-9]+\.[0-9]+\.[0-9]+$'; then + return 0 + fi + fi + # If the second parameter is nonzero, then we will skip + # the domain name check: + if [ "${2:-0}" -eq 0 ]; then + # We consider all the names of a given host to be local addresses: + [ "$1" = "$(hostname -s)" -o \ + "$1" = "$(hostname -f)" -o \ + "$1" = "$(hostname -d)" ] && return 0 + fi + # Now let's check if the given address is assigned to + # one of the network cards: local ip_util="$(command -v ip)" if [ -n "$ip_util" ]; then # ip address show ouput format is " inet[6]
/": @@ -914,7 +936,6 @@ is_local_ip() | grep -F " $1 " >/dev/null && return 0 fi fi - return 1 } diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 92f77eec331..f954046382c 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -250,19 +250,27 @@ else CAFILE_OPT="" fi +VERIFY_OPT="" +CHECK_OPT="" +CHECK_OPT_LOCAL="" if [ "${SSLMODE#VERIFY}" != "$SSLMODE" ] then case "$SSLMODE" in 'VERIFY_IDENTITY') VERIFY_OPT='verifyPeer = yes' - CHECK_OPT="" ;; 'VERIFY_CA') VERIFY_OPT='verifyChain = yes' - if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then - CHECK_OPT='checkHost = localhost' + # check if the address is an ip-address (v4 or v6): + if echo "$WSREP_SST_OPT_HOST_UNESCAPED" | \ + grep -q -E '^([0-9]+(\.[0-9]+){3,3}|[0-9a-fA-F]?(\:[0-9a-fA-F]*)+)$' + then + CHECK_OPT="checkIP = $WSREP_SST_OPT_HOST_UNESCAPED" else - CHECK_OPT='checkHost = $WSREP_SST_OPT_HOST_UNESCAPED' + CHECK_OPT="checkHost = $WSREP_SST_OPT_HOST" + fi + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT_LOCAL="checkHost = localhost" fi ;; *) @@ -273,9 +281,6 @@ then wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file" exit 22 # EINVAL fi -else - VERIFY_OPT="" - CHECK_OPT="" fi STUNNEL="" @@ -310,6 +315,7 @@ connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 ${VERIFY_OPT} ${CHECK_OPT} +${CHECK_OPT_LOCAL} EOF fi @@ -566,6 +572,9 @@ foreground = yes pid = $STUNNEL_PID debug = warning client = no +${VERIFY_OPT} +${CHECK_OPT} +${CHECK_OPT_LOCAL} [rsync] accept = $STUNNEL_ACCEPT exec = $(command -v rsync) @@ -583,6 +592,9 @@ foreground = yes pid = $STUNNEL_PID debug = warning client = no +${VERIFY_OPT} +${CHECK_OPT} +${CHECK_OPT_LOCAL} [rsync] accept = $STUNNEL_ACCEPT exec = $SHELL -- cgit v1.2.1 From 98f7b2cb09d73758e3f2af6d57b6e0a804387d3e Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Fri, 14 May 2021 14:13:59 +0530 Subject: MDEV-25663 Double free of transaction during truncate operation InnoDB truncate table fails to load the fts stopword table into cache. In that case, InnoDB double frees the truncate creation transaction. InnoDB should free the transaction which was created inside ha_innobase::create. --- storage/innobase/handler/ha_innodb.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index b7c6fa98b1c..f19bde12714 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -13024,7 +13024,6 @@ create_table_info_t::create_table_update_dict() if (!innobase_fts_load_stopword(innobase_table, NULL, m_thd)) { dict_table_close(innobase_table, FALSE, FALSE); srv_active_wake_master_thread(); - trx_free_for_mysql(m_trx); DBUG_RETURN(-1); } @@ -13169,6 +13168,12 @@ ha_innobase::create( error = info.create_table_update_dict(); + /* In case of error, free the transaction only if + it is newly created transaction in ha_innobase::create() */ + if (own_trx && error) { + trx_free_for_mysql(info.trx()); + } + /* Tell the InnoDB server that there might be work for utility threads: */ -- cgit v1.2.1 From 349d77ecdd8ed549005ac000a80e3e0126fbd80a Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Thu, 20 May 2021 15:47:21 +0530 Subject: MDEV-25721 Double free of table when inplace alter FTS add index fails Problem: ======== InnoDB double frees the table if auxiliary fts table creation fails and fails to set the dict operation for the transaction. It leads to failure while dropping newly added index. Solution: ========= InnoDB should avoid double freeing and set the dictionary operation of transaction in fts_create_common_tables() --- .../suite/innodb_fts/r/innodb-fts-ddl.result | 12 ++++++++ mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt | 1 + mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test | 20 +++++++++++++ storage/innobase/fts/fts0fts.cc | 33 ++++++++++++++-------- 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt diff --git a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result index f1e625037f3..4efab17d441 100644 --- a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result +++ b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result @@ -266,3 +266,15 @@ t1 CREATE TABLE `t1` ( `f1` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 DROP TABLE t1; +# +# MDEV-25271 Double free of table when inplace alter +# FTS add index fails +# +call mtr.add_suppression("InnoDB: Operating system error number .* in a file operation."); +call mtr.add_suppression("InnoDB: Error number .* means"); +call mtr.add_suppression("InnoDB: Cannot create file"); +call mtr.add_suppression("InnoDB: Failed to create"); +CREATE TABLE t1(a TEXT, FTS_DOC_ID BIGINT UNSIGNED NOT NULL UNIQUE) ENGINE=InnoDB; +ALTER TABLE t1 ADD FULLTEXT(a), ALGORITHM=INPLACE; +ERROR HY000: Got error 11 "Resource temporarily unavailable" from storage engine InnoDB +DROP TABLE t1; diff --git a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt new file mode 100644 index 00000000000..e6ae8d0fe0a --- /dev/null +++ b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt @@ -0,0 +1 @@ +--enable-plugin-innodb-sys-tables diff --git a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test index e055acc4968..824e719b7a8 100644 --- a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test +++ b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test @@ -319,3 +319,23 @@ ALTER TABLE t1 ADD FTS_DOC_ID INT UNSIGNED NOT NULL, ALGORITHM=INPLACE; SHOW CREATE TABLE t1; DROP TABLE t1; + + +--echo # +--echo # MDEV-25271 Double free of table when inplace alter +--echo # FTS add index fails +--echo # +call mtr.add_suppression("InnoDB: Operating system error number .* in a file operation."); +call mtr.add_suppression("InnoDB: Error number .* means"); +call mtr.add_suppression("InnoDB: Cannot create file"); +call mtr.add_suppression("InnoDB: Failed to create"); + +let MYSQLD_DATADIR=`select @@datadir`; +CREATE TABLE t1(a TEXT, FTS_DOC_ID BIGINT UNSIGNED NOT NULL UNIQUE) ENGINE=InnoDB; +let $fts_aux_file= `select concat('FTS_',right(concat(repeat('0',16), lower(hex(TABLE_ID))),16),'_BEING_DELETED.ibd') FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME='test/t1'`; +write_file $MYSQLD_DATADIR/test/$fts_aux_file; +EOF +--error ER_GET_ERRNO +ALTER TABLE t1 ADD FULLTEXT(a), ALGORITHM=INPLACE; +DROP TABLE t1; +remove_file $MYSQLD_DATADIR/test/$fts_aux_file; diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index a74b5083128..53928cac5dd 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -1787,7 +1787,6 @@ fts_create_one_common_table( error = row_create_table_for_mysql(new_table, trx, FIL_ENCRYPTION_DEFAULT, FIL_DEFAULT_ENCRYPTION_KEY); - if (error == DB_SUCCESS) { dict_index_t* index = dict_mem_index_create( @@ -1808,17 +1807,22 @@ fts_create_one_common_table( error = row_create_index_for_mysql(index, trx, NULL); trx->dict_operation = op; + } else { +err_exit: + new_table = NULL; + ib::warn() << "Failed to create FTS common table " + << fts_table_name; + trx->error_state = error; + return NULL; } if (error != DB_SUCCESS) { dict_mem_table_free(new_table); - new_table = NULL; - ib::warn() << "Failed to create FTS common table " - << fts_table_name; trx->error_state = DB_SUCCESS; row_drop_table_for_mysql(fts_table_name, trx, SQLCOM_DROP_DB); - trx->error_state = error; + goto err_exit; } + return(new_table); } @@ -1866,6 +1870,8 @@ fts_create_common_tables( FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table); + op = trx_get_dict_operation(trx); + error = fts_drop_common_tables(trx, &fts_table); if (error != DB_SUCCESS) { @@ -1882,6 +1888,7 @@ fts_create_common_tables( trx, table, full_name[i], fts_table.suffix, heap); if (common_table == NULL) { + trx->error_state = DB_SUCCESS; error = DB_ERROR; goto func_exit; } else { @@ -1926,8 +1933,6 @@ fts_create_common_tables( error = row_create_index_for_mysql(index, trx, NULL); - trx->dict_operation = op; - func_exit: if (error != DB_SUCCESS) { for (it = common_tables.begin(); it != common_tables.end(); @@ -1937,6 +1942,8 @@ func_exit: } } + trx->dict_operation = op; + common_tables.clear(); mem_heap_free(heap); @@ -2019,16 +2026,20 @@ fts_create_one_index_table( error = row_create_index_for_mysql(index, trx, NULL); trx->dict_operation = op; + } else { +err_exit: + new_table = NULL; + ib::warn() << "Failed to create FTS index table " + << table_name; + trx->error_state = error; + return NULL; } if (error != DB_SUCCESS) { dict_mem_table_free(new_table); - new_table = NULL; - ib::warn() << "Failed to create FTS index table " - << table_name; trx->error_state = DB_SUCCESS; row_drop_table_for_mysql(table_name, trx, SQLCOM_DROP_DB); - trx->error_state = error; + goto err_exit; } return(new_table); -- cgit v1.2.1 From 15214a4f11a4081dd716c60bc163eeddc2024149 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 23 May 2021 19:30:05 +0300 Subject: MDEV-25708 THD::cleanup(): Assertion `!mdl_context.has_locks()' failed The reason was that WSREP code in mysql_create_or_drop_trigge() did jump to an exit label that did not do proper cleanup of state. Fixed by ensuring that also WSREP code goes trough the cleanup process. --- sql/sql_trigger.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 19379d3f77a..b8042a90872 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -457,7 +457,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) thd->variables.lock_wait_timeout)) goto end; - if (!create) { bool if_exists= thd->lex->if_exists(); @@ -565,7 +564,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) #ifdef WITH_WSREP if (WSREP(thd) && !wsrep_should_replicate_ddl(thd, table->s->db_type()->db_type)) - goto wsrep_error_label; + goto end; #endif /* Later on we will need it to downgrade the lock */ @@ -652,9 +651,11 @@ end: thd->mdl_context.release_lock(mdl_request_for_trn.ticket); DBUG_RETURN(result); + #ifdef WITH_WSREP wsrep_error_label: - DBUG_RETURN(true); + DBUG_ASSERT(result == 1); + goto end; #endif } -- cgit v1.2.1 From 5a20b30fb306882718dee307571269502ceb2720 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 23 May 2021 19:41:17 +0300 Subject: MDEV-25738 Assertion `ticket->m_duration == MDL_EXPLICIT' failed No crash (probably fixed before). Added test case --- mysql-test/main/trigger-trans.result | 13 +++++++++++++ mysql-test/main/trigger-trans.test | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/mysql-test/main/trigger-trans.result b/mysql-test/main/trigger-trans.result index c58c4230a40..24ef9a4291a 100644 --- a/mysql-test/main/trigger-trans.result +++ b/mysql-test/main/trigger-trans.result @@ -229,3 +229,16 @@ INSERT INTO t2 (id) VALUES (1); disconnect con2; connection default; DROP TABLE t3, t2, t1; +# +# MDEV-25738 Assertion `ticket->m_duration == MDL_EXPLICIT' failed in +# void MDL_context::release_lock(MDL_ticket*) +# +CREATE TABLE t1 (id int(11)) ENGINE=InnoDB; +SET max_statement_time= 0.001; +LOCK TABLES t1 WRITE; +CREATE TRIGGER tr16 AFTER UPDATE ON t1 FOR EACH ROW INSERT INTO t1 VALUES (1); +DROP TABLE t1; +SET max_statement_time= default; +# +# End of 10.5 tests +# diff --git a/mysql-test/main/trigger-trans.test b/mysql-test/main/trigger-trans.test index 17656c3516e..378da045e0a 100644 --- a/mysql-test/main/trigger-trans.test +++ b/mysql-test/main/trigger-trans.test @@ -233,3 +233,19 @@ DROP TABLE t3, t2, t1; # Wait till we reached the initial number of concurrent sessions --source include/wait_until_count_sessions.inc + +--echo # +--echo # MDEV-25738 Assertion `ticket->m_duration == MDL_EXPLICIT' failed in +--echo # void MDL_context::release_lock(MDL_ticket*) +--echo # + +CREATE TABLE t1 (id int(11)) ENGINE=InnoDB; +SET max_statement_time= 0.001; +LOCK TABLES t1 WRITE; +CREATE TRIGGER tr16 AFTER UPDATE ON t1 FOR EACH ROW INSERT INTO t1 VALUES (1); +DROP TABLE t1; +SET max_statement_time= default; + +--echo # +--echo # End of 10.5 tests +--echo # -- cgit v1.2.1 From 30c90890953154e4120fb0373391288ce76dc7a5 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 23 May 2021 19:31:06 +0300 Subject: Fixed compiler warnings from CONNECT --- storage/connect/bson.cpp | 8 ++++---- storage/connect/bsonudf.cpp | 23 ++++++++++++----------- storage/connect/tabbson.cpp | 11 ++++++----- storage/connect/tabjson.cpp | 4 +--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/storage/connect/bson.cpp b/storage/connect/bson.cpp index a0a421657bd..132c7fdd51f 100644 --- a/storage/connect/bson.cpp +++ b/storage/connect/bson.cpp @@ -83,7 +83,7 @@ BDOC::BDOC(PGLOBAL G) : BJSON(G, NULL) PBVAL BDOC::ParseJson(PGLOBAL g, char* js, size_t lng) { size_t i; - bool b = false, ptyp = (bool *)pty; + bool b = false; PBVAL bvp = NULL; s = js; @@ -145,7 +145,7 @@ PBVAL BDOC::ParseJson(PGLOBAL g, char* js, size_t lng) b = false; break; } // endif b - + /* fall through */ default: if (bvp->Type != TYPE_UNKNOWN) { bvp->To_Val = ParseAsArray(i); @@ -683,7 +683,7 @@ bool BDOC::SerializeArray(OFFSET arp, bool b) } else if (jp->WriteChr('[')) return true; - for (vp; vp; vp = MVP(vp->Next)) { + for (; vp; vp = MVP(vp->Next)) { if (first) first = false; else if ((!b || jp->Prty()) && jp->WriteChr(',')) @@ -718,7 +718,7 @@ bool BDOC::SerializeObject(OFFSET obp) if (jp->WriteChr('{')) return true; - for (prp; prp; prp = GetNext(prp)) { + for (; prp; prp = GetNext(prp)) { if (first) first = false; else if (jp->WriteChr(',')) diff --git a/storage/connect/bsonudf.cpp b/storage/connect/bsonudf.cpp index 39bcc18281b..a7d8f811975 100644 --- a/storage/connect/bsonudf.cpp +++ b/storage/connect/bsonudf.cpp @@ -87,6 +87,7 @@ static PBSON BbinAlloc(PGLOBAL g, ulong len, PBVAL jsp) /*********************************************************************************/ /* SubAlloc a new BJNX class with protection against memory exhaustion. */ /*********************************************************************************/ +#ifdef NOT_USED static PBJNX BjnxNew(PGLOBAL g, PBVAL vlp, int type, int len) { PBJNX bjnx; @@ -103,7 +104,7 @@ static PBJNX BjnxNew(PGLOBAL g, PBVAL vlp, int type, int len) return bjnx; } /* end of BjnxNew */ - +#endif /* ----------------------------------- BSNX ------------------------------------ */ /*********************************************************************************/ @@ -286,7 +287,7 @@ my_bool BJNX::ParseJpath(PGLOBAL g) { char* p, * p1 = NULL, * p2 = NULL, * pbuf = NULL; int i; - my_bool a, mul = false; + my_bool a; if (Parsed) return false; // Already done @@ -497,7 +498,8 @@ void BJNX::SetJsonValue(PGLOBAL g, PVAL vp, PBVAL vlp) break; case TYPE_NULL: vp->SetNull(true); - default: + /* fall through */ + default: vp->Reset(); } // endswitch Type @@ -540,7 +542,6 @@ PVAL BJNX::GetColumnValue(PGLOBAL g, PBVAL row, int i) /*********************************************************************************/ PBVAL BJNX::GetRowValue(PGLOBAL g, PBVAL row, int i) { - my_bool expd = false; PBVAL bap; PBVAL vlp = NULL; @@ -1083,7 +1084,7 @@ my_bool BJNX::CheckPath(PGLOBAL g, UDF_ARGS *args, PBVAL jsp, PBVAL& jvp, int n) PSZ BJNX::Locate(PGLOBAL g, PBVAL jsp, PBVAL jvp, int k) { PSZ str = NULL; - my_bool b = false, err = true; + my_bool err = true; g->Message[0] = 0; @@ -1204,7 +1205,7 @@ my_bool BJNX::LocateValue(PGLOBAL g, PBVAL jvp) PSZ BJNX::LocateAll(PGLOBAL g, PBVAL jsp, PBVAL bvp, int mx) { PSZ str = NULL; - my_bool b = false, err = true; + my_bool err = true; PJPN jnp; if (!jsp) { @@ -2894,7 +2895,7 @@ my_bool bson_array_grp_init(UDF_INIT *initid, UDF_ARGS *args, char *message) return true; PGLOBAL g = (PGLOBAL)initid->ptr; - PBJNX bxp = new(g) BJNX(g); + (void) new(g) BJNX(g); JsonMemSave(g); return false; @@ -2967,7 +2968,7 @@ my_bool bson_object_grp_init(UDF_INIT *initid, UDF_ARGS *args, char *message) return true; PGLOBAL g = (PGLOBAL)initid->ptr; - PBJNX bxp = new(g) BJNX(g); + (void) new(g) BJNX(g); JsonMemSave(g); return false; @@ -3037,7 +3038,7 @@ my_bool bson_test_init(UDF_INIT* initid, UDF_ARGS* args, char* message) { char* bson_test(UDF_INIT* initid, UDF_ARGS* args, char* result, unsigned long* res_length, char* is_null, char* error) { - char* str = NULL, * sap = NULL, * fn = NULL; + char* str = NULL, * fn = NULL; int pretty = 1; PBVAL bvp; PGLOBAL g = (PGLOBAL)initid->ptr; @@ -5035,7 +5036,7 @@ char* bbin_array_add_values(UDF_INIT* initid, UDF_ARGS* args, char* result, if (!CheckMemory(g, initid, args, args->arg_count, true)) { uint i = 0; BJNX bnx(g); - PBVAL arp, top, jvp = NULL; + PBVAL arp, top; PBVAL bvp = bnx.MakeValue(args, 0, true, &top); if (bvp->Type == TYPE_JAR) { @@ -5659,7 +5660,7 @@ char *bbin_get_item(UDF_INIT *initid, UDF_ARGS *args, char *result, if (g->Xchk) { bsp = (PBSON)g->Xchk; } else if (!CheckMemory(g, initid, args, 1, true, true)) { - char *path = MakePSZ(g, args, 1); + // char *path = MakePSZ(g, args, 1); BJNX bnx(g, NULL, TYPE_STRING, initid->max_length); PBVAL top, jvp = NULL; PBVAL jsp = bnx.MakeValue(args, 0, true, &top); diff --git a/storage/connect/tabbson.cpp b/storage/connect/tabbson.cpp index 8477d22d364..ce6d1d3b670 100644 --- a/storage/connect/tabbson.cpp +++ b/storage/connect/tabbson.cpp @@ -838,7 +838,7 @@ PBVAL BCUTIL::MakeBson(PGLOBAL g, PBVAL jsp, int n) /***********************************************************************/ PBVAL BCUTIL::GetRowValue(PGLOBAL g, PBVAL row, int i) { - int nod = Cp->Nod, n = nod - 1; + int nod = Cp->Nod; JNODE *nodes = Cp->Nodes; PBVAL arp; PBVAL bvp = NULL; @@ -1970,6 +1970,7 @@ PSZ BSONCOL::GetJpath(PGLOBAL g, bool proj) return NULL; for (p1 = p2 = mgopath; *p1; p1++) + { if (i) { // Inside [] if (isdigit(*p1)) { if (!proj) @@ -2007,14 +2008,14 @@ PSZ BSONCOL::GetJpath(PGLOBAL g, bool proj) p2--; // Suppress last :* break; } // endif p2 - + /* fall through */ default: *p2++ = *p1; break; } // endswitch p1; - - *p2 = 0; - return mgopath; + } + *p2 = 0; + return mgopath; } else return NULL; diff --git a/storage/connect/tabjson.cpp b/storage/connect/tabjson.cpp index 07c54e8a0fb..737326dfcfe 100644 --- a/storage/connect/tabjson.cpp +++ b/storage/connect/tabjson.cpp @@ -1038,7 +1038,7 @@ bool TDBJSN::OpenDB(PGLOBAL g) /*********************************************************************/ /* Lrecl is Ok. */ /*********************************************************************/ - size_t linelen = Lrecl; + MODE mode = Mode; // Buffer must be allocated in g->Sarea @@ -1683,7 +1683,6 @@ PVAL JSONCOL::MakeJson(PGLOBAL g, PJSON jsp, int n) /***********************************************************************/ PJVAL JSONCOL::GetRowValue(PGLOBAL g, PJSON row, int i) { - int n = Nod - 1; PJVAL val = NULL; for (; i < Nod && row; i++) { @@ -1810,7 +1809,6 @@ void JSONCOL::ReadColumn(PGLOBAL g) /***********************************************************************/ PVAL JSONCOL::GetColumnValue(PGLOBAL g, PJSON row, int i) { - int n = Nod - 1; PJAR arp; PJVAL val = NULL; -- cgit v1.2.1 From c80cecb5e3e509d37929b4f446edf9b6c636b98f Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 23 May 2021 19:53:38 +0300 Subject: Updated BUILD scripts to update modules wsrep-lib and columnstore Other things - Added lost option '--just-clean' --- BUILD/FINISH.sh | 8 +++++++- BUILD/SETUP.sh | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/BUILD/FINISH.sh b/BUILD/FINISH.sh index ea5ca44d187..05b2a5fcd3b 100644 --- a/BUILD/FINISH.sh +++ b/BUILD/FINISH.sh @@ -44,7 +44,13 @@ cd ../storage/rocksdb/rocksdb git submodule update cd ../../maria/libmarias3 git submodule update -cd ../../.." +cd ../../.. +cd storage/columnstore/columnstore +git submodule update +cd ../../.. +cd wsrep-lib +git submodule update +cd .." fi commands="$commands path=`dirname $0` diff --git a/BUILD/SETUP.sh b/BUILD/SETUP.sh index 9dbb4edf75c..cfcb771859f 100755 --- a/BUILD/SETUP.sh +++ b/BUILD/SETUP.sh @@ -32,6 +32,7 @@ Usage: $0 [-h|-n] [configure-options] -n, --just-print Don't actually run any commands; just print them. -c, --just-configure Stop after running configure. Combined with --just-print shows configure options. + --just-clean Clean up compilation files and update sub modules --extra-configs=xxx Add this to configure options --extra-flags=xxx Add this C and CXX flags --extra-cflags=xxx Add this to C flags @@ -71,6 +72,8 @@ parse_options() just_configure=1;; -n | --just-print | --print) just_print=1;; + --just-clean) + just_clean=1;; --verbose) verbose_make=1;; -h | --help) @@ -94,6 +97,7 @@ fi prefix="/usr/local/mysql" just_print= +just_clean= just_configure= warning_mode= maintainer_mode= -- cgit v1.2.1 From 5c75ba9cadc7877e91d6b712f157ff5623c09c60 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Mon, 24 May 2021 11:33:01 +0530 Subject: MDEV-25663 Double free of transaction during truncate operation - Patch addresses the problem to fix double free of transaction if it is own transaction. --- storage/innobase/handler/ha_innodb.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index f19bde12714..5617a293a23 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -13155,10 +13155,6 @@ ha_innobase::create( innobase_commit_low(trx); row_mysql_unlock_data_dictionary(trx); - if (own_trx) { - trx_free_for_mysql(trx); - } - /* Flush the log to reduce probability that the .frm files and the InnoDB data dictionary get out-of-sync if the user runs with innodb_flush_log_at_trx_commit = 0 */ @@ -13168,10 +13164,8 @@ ha_innobase::create( error = info.create_table_update_dict(); - /* In case of error, free the transaction only if - it is newly created transaction in ha_innobase::create() */ - if (own_trx && error) { - trx_free_for_mysql(info.trx()); + if (own_trx) { + trx_free_for_mysql(trx); } /* Tell the InnoDB server that there might be work for -- cgit v1.2.1 From 2eb357496c9364256e19e1019e228a3a3d95de8b Mon Sep 17 00:00:00 2001 From: nia Date: Mon, 24 May 2021 15:35:06 +0200 Subject: my_largepage: Fix build with MAP_ALIGNED by no MAP_ALIGNED_SUPER This needs backporting to MariaDB 10.5. Any changes I submit are freely available under the new BSD license. Signed-off-by: Nia Alarie --- mysys/my_largepage.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mysys/my_largepage.c b/mysys/my_largepage.c index c3fc97ffe0a..0fdc4e17a26 100644 --- a/mysys/my_largepage.c +++ b/mysys/my_largepage.c @@ -336,8 +336,10 @@ uchar *my_large_malloc(size_t *size, myf my_flags) # warning "No explicit large page (HUGETLB pages) support in Linux < 3.8" #endif #elif defined(MAP_ALIGNED) - mapflag|= MAP_ALIGNED_SUPER | - MAP_ALIGNED(my_bit_log2_size_t(large_page_size)); + mapflag|= MAP_ALIGNED(my_bit_log2_size_t(large_page_size)); +#if defined(MAP_ALIGNED_SUPER) + mapflag|= MAP_ALIGNED_SUPER; +#endif #endif aligned_size= MY_ALIGN(*size, (size_t) large_page_size); } -- cgit v1.2.1 From 04de651725c3eeee8f216c55e2f8133e4547fadb Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Tue, 25 May 2021 00:43:03 -0700 Subject: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist In the code existed just before this patch binding of a table reference to the specification of the corresponding CTE happens in the function open_and_process_table(). If the table reference is not the first in the query the specification is cloned in the same way as the specification of a view is cloned for any reference of the view. This works fine for standalone queries, but does not work for stored procedures / functions for the following reason. When the first call of a stored procedure/ function SP is processed the body of SP is parsed. When a query of SP is parsed the info on each encountered table reference is put into a TABLE_LIST object linked into a global chain associated with the query. When parsing of the query is finished the basic info on the table references from this chain except table references to derived tables and information schema tables is put in one hash table associated with SP. When parsing of the body of SP is finished this hash table is used to construct TABLE_LIST objects for all table references mentioned in SP and link them into the list of such objects passed to a pre-locking process that calls open_and_process_table() for each table from the list. When a TABLE_LIST for a view is encountered the view is opened and its specification is parsed. For any table reference occurred in the specification a new TABLE_LIST object is created to be included into the list for pre-locking. After all objects in the pre-locking have been looked through the tables mentioned in the list are locked. Note that the objects referenced CTEs are just skipped here as it is impossible to resolve these references without any info on the context where they occur. Now the statements from the body of SP are executed one by one that. At the very beginning of the execution of a query the tables used in the query are opened and open_and_process_table() now is called for each table reference mentioned in the list of TABLE_LIST objects associated with the query that was built when the query was parsed. For each table reference first the reference is checked against CTEs definitions in whose scope it occurred. If such definition is found the reference is considered resolved and if this is not the first reference to the found CTE the the specification of the CTE is re-parsed and the result of the parsing is added to the parsing tree of the query as a sub-tree. If this sub-tree contains table references to other tables they are added to the list of TABLE_LIST objects associated with the query in order the referenced tables to be opened. When the procedure that opens the tables comes to the TABLE_LIST object created for a non-first reference to a CTE it discovers that the referenced table instance is not locked and reports an error. Thus processing non-first table references to a CTE similar to how references to view are processed does not work for queries used in stored procedures / functions. And the main problem is that the current pre-locking mechanism employed for stored procedures / functions does not allow to save the context in which a CTE reference occur. It's not trivial to save the info about the context where a CTE reference occurs while the resolution of the table reference cannot be done without this context and consequentially the specification for the table reference cannot be determined. This patch solves the above problem by moving resolution of all CTE references at the parsing stage. More exactly references to CTEs occurred in a query are resolved right after parsing of the query has finished. After resolution any CTE reference it is marked as a reference to to derived table. So it is excluded from the hash table created for pre-locking used base tables and view when the first call of a stored procedure / function is processed. This solution required recursive calls of the parser. The function THD::sql_parser() has been added specifically for recursive invocations of the parser. --- mysql-test/main/cte_nonrecursive.result | 201 +++++++++++++++ mysql-test/main/cte_nonrecursive.test | 202 +++++++++++++++ sql/item_subselect.cc | 1 - sql/sp_head.cc | 3 +- sql/sql_base.cc | 33 +-- sql/sql_class.cc | 56 +++++ sql/sql_class.h | 8 +- sql/sql_cte.cc | 420 ++++++++++++++++++++++---------- sql/sql_cte.h | 85 ++++++- sql/sql_lex.cc | 9 +- sql/sql_lex.h | 25 +- sql/sql_parse.cc | 15 +- sql/sql_prepare.cc | 3 - sql/sql_view.cc | 9 - sql/sql_yacc.yy | 26 +- sql/sql_yacc_ora.yy | 26 +- sql/table.h | 37 +++ 17 files changed, 948 insertions(+), 211 deletions(-) diff --git a/mysql-test/main/cte_nonrecursive.result b/mysql-test/main/cte_nonrecursive.result index f50ac50ded9..d6470573bb7 100644 --- a/mysql-test/main/cte_nonrecursive.result +++ b/mysql-test/main/cte_nonrecursive.result @@ -1763,6 +1763,207 @@ a c 2 1 7 3 drop table t1; +# +# MDEV-23886: Stored Function returning the result of a query +# that uses CTE over a table twice +# +create table t1 (c1 int); +insert into t1 values (1),(2),(6); +create function f1() returns int return +( with cte1 as (select c1 from t1) +select sum(c1) from +(select * from cte1 union all select * from cte1) dt +); +select f1(); +f1() +18 +create function f2() returns int return +( with cte1 as (select c1 from t1) +select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); +f2() +9 +create function f3() returns int return +( with cte1 as (select c1 from t1) +select +case +when exists(select 1 from cte1 where c1 between 1 and 2) then 1 +when exists(select 1 from cte1 where c1 between 5 and 6) then 2 +else 0 +end +); +select f3(); +f3() +1 +create view v1 as (select c1 from t1); +create function f4() returns int return +( select sum(c1) from +(select * from v1 union all select * from v1) dt +); +select f4(); +f4() +18 +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); +f5() +9 +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; +create function f6() returns int return +(select s from v2); +select f6(); +f6() +18 +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); +f7() +18 +select f5() + f6(); +f5() + f6() +27 +prepare stmt from "select f5() + f6();"; +execute stmt; +f5() + f6() +27 +execute stmt; +f5() + f6() +27 +deallocate prepare stmt; +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; +drop view v1; +drop view v2; +create table t2 (a int, b int); +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; +select * from t2; +a b +6 6 +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +drop procedure p1; +# checking CTE resolution for queries with hanging CTEs +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; +a b +1 2 +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +ERROR 42S22: Unknown column 'c1' in 'where clause' +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; +ERROR 42S22: Unknown column 'cte2.c1' in 'where clause' +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; +a b +1 1 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; +a b +6 6 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; +ERROR 23000: Column 'c1' in where clause is ambiguous +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; +a b +1 1 +2 1 +1 2 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; +a b +6 6 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; +ERROR 42S22: Unknown column 'r1.c1' in 'field list' +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +2 2 +drop procedure p1; +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +ERROR 42S22: Unknown column 'a' in 'field list' +drop procedure p1; +drop table t1,t2; # End of 10.2 tests # # MDEV-21673: several references to CTE that uses diff --git a/mysql-test/main/cte_nonrecursive.test b/mysql-test/main/cte_nonrecursive.test index 49df2bb9ec0..c2a0a396717 100644 --- a/mysql-test/main/cte_nonrecursive.test +++ b/mysql-test/main/cte_nonrecursive.test @@ -1261,6 +1261,208 @@ select a, c from cte as r2 where a > 4; drop table t1; +--echo # +--echo # MDEV-23886: Stored Function returning the result of a query +--echo # that uses CTE over a table twice +--echo # + +create table t1 (c1 int); +insert into t1 values (1),(2),(6); + +create function f1() returns int return +( with cte1 as (select c1 from t1) + select sum(c1) from + (select * from cte1 union all select * from cte1) dt +); +select f1(); + +create function f2() returns int return +( with cte1 as (select c1 from t1) + select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); + +create function f3() returns int return +( with cte1 as (select c1 from t1) + select + case + when exists(select 1 from cte1 where c1 between 1 and 2) then 1 + when exists(select 1 from cte1 where c1 between 5 and 6) then 2 + else 0 + end +); +select f3(); + +create view v1 as (select c1 from t1); + +create function f4() returns int return +( select sum(c1) from + (select * from v1 union all select * from v1) dt +); +select f4(); + +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); + +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; + +create function f6() returns int return +(select s from v2); +select f6(); + +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); + +select f5() + f6(); + +prepare stmt from "select f5() + f6();"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; + +drop view v1; +drop view v2; + +create table t2 (a int, b int); + +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; + +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +--echo # checking CTE resolution for queries with hanging CTEs + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; + +select * from t2; + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; + +--error ER_NON_UNIQ_ERROR +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +--error ER_BAD_FIELD_ERROR +call p1(); + +drop procedure p1; + +drop table t1,t2; + --echo # End of 10.2 tests --echo # diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 53a6847c52f..0fd64542e17 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1810,7 +1810,6 @@ double Item_in_subselect::val_real() As far as Item_in_subselect called only from Item_in_optimizer this method should not be used */ - DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); if (forced_const) return value; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index aa4f80907f5..3ea49386d1b 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3411,8 +3411,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, Json_writer_object trace_command(thd); Json_writer_array trace_command_steps(thd, "steps"); if (open_tables) - res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) || - instr->exec_open_and_lock_tables(thd, m_lex->query_tables); + res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); if (likely(!res)) { diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 41f02fbed92..d8baa118ef4 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3695,7 +3695,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, if (tables->derived) { if (!tables->view) + { + if (!tables->is_derived()) + tables->set_derived(); goto end; + } /* We restore view's name and database wiped out by derived tables processing and fall back to standard open process in order to @@ -3705,35 +3709,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, tables->db= tables->view_db; tables->table_name= tables->view_name; } - else if (tables->select_lex) - { - /* - Check whether 'tables' refers to a table defined in a with clause. - If so set the reference to the definition in tables->with. - */ - if (!tables->with) - tables->with= tables->select_lex->find_table_def_in_with_clauses(tables); - /* - If 'tables' is defined in a with clause set the pointer to the - specification from its definition in tables->derived. - */ - if (tables->with) - { - if (tables->is_recursive_with_table() && - !tables->is_with_table_recursive_reference()) - { - tables->with->rec_outer_references++; - With_element *with_elem= tables->with; - while ((with_elem= with_elem->get_next_mutually_recursive()) != - tables->with) - with_elem->rec_outer_references++; - } - if (tables->set_as_with_table(thd, tables->with)) - DBUG_RETURN(1); - else - goto end; - } - } if (!tables->derived && is_infoschema_db(&tables->db)) { diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7864623e719..7e43605b047 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2790,6 +2790,62 @@ void THD::close_active_vio() #endif +/* + @brief MySQL parser used for recursive invocations + + @param old_lex The LEX structure in the state when this parser + is called recursively + @param lex The LEX structure used to parse a new SQL fragment + @param str The SQL fragment to parse + @param str_len The length of the SQL fragment to parse + @param stmt_prepare_mode true <=> when parsing a prepare statement + + @details + This function is to be used when parsing of an SQL fragment is + needed within one of the grammar rules. + + @notes + Currently the function is used only when the specification of a CTE + is parsed for the not first and not recursive references of the CTE. + + @retval false On a successful parsing of the fragment + @retval true Otherwise +*/ + +bool THD::sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode) +{ + extern int MYSQLparse(THD * thd); + extern int ORAparse(THD * thd); + + bool parse_status= false; + Parser_state parser_state; + Parser_state *old_parser_state= m_parser_state; + + if (parser_state.init(this, str, str_len)) + return true; + + m_parser_state= &parser_state; + parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; + parser_state.m_lip.multi_statements= false; + parser_state.m_lip.m_digest= NULL; + + lex->param_list= old_lex->param_list; + lex->sphead= old_lex->sphead; + lex->spname= old_lex->spname; + lex->spcont= old_lex->spcont; + lex->sp_chistics= old_lex->sp_chistics; + lex->trg_chistics= old_lex->trg_chistics; + + parse_status= (variables.sql_mode & MODE_ORACLE) ? + ORAparse(this) : MYSQLparse(this) != 0; + + m_parser_state= old_parser_state; + + return parse_status; +} + + struct Item_change_record: public ilink { Item **place; diff --git a/sql/sql_class.h b/sql/sql_class.h index f754524332d..cb34067298c 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4244,14 +4244,11 @@ public: to resolve all CTE names as we don't need this message to be thrown for any CTE references. */ - if (!lex->with_clauses_list) + if (!lex->with_cte_resolution) { my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); return TRUE; } - /* This will allow to throw an error later for non-CTE references */ - to->str= NULL; - to->length= 0; return FALSE; } @@ -5047,6 +5044,9 @@ public: Item *sp_prepare_func_item(Item **it_addr, uint cols= 1); bool sp_eval_expr(Field *result_field, Item **expr_item_ptr); + bool sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode); + }; /** A short cut for thd->get_stmt_da()->set_ok_status(). */ diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index f3861ebd3e9..dfcb4e18772 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -83,7 +83,7 @@ void st_select_lex_unit::set_with_clause(With_clause *with_cl) true on failure */ -bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) +bool LEX::check_dependencies_in_with_clauses() { for (With_clause *with_clause= with_clauses_list; with_clause; @@ -99,6 +99,201 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) } +/** + @brief + Resolve references to CTE in specification of hanging CTE + + @details + A CTE to which there are no references in the query is called hanging CTE. + Although such CTE is not used for execution its specification must be + subject to context analysis. All errors concerning references to + non-existing tables or fields occurred in the specification must be + reported as well as all other errors caught at the prepare stage. + The specification of a hanging CTE might contain references to other + CTE outside of the specification and within it if the specification + contains a with clause. This function resolves all such references for + all hanging CTEs encountered in the processed query. + + @retval + false on success + true on failure +*/ + +bool +LEX::resolve_references_to_cte_in_hanging_cte() +{ + for (With_clause *with_clause= with_clauses_list; + with_clause; with_clause= with_clause->next_with_clause) + { + for (With_element *with_elem= with_clause->with_list.first; + with_elem; with_elem= with_elem->next) + { + if (!with_elem->is_referenced()) + { + TABLE_LIST *first_tbl= + with_elem->spec->first_select()->table_list.first; + TABLE_LIST **with_elem_end_pos= with_elem->head->tables_pos.end_pos; + if (first_tbl && resolve_references_to_cte(first_tbl, with_elem_end_pos)) + return true; + } + } + } + return false; +} + + +/** + @brief + Resolve table references to CTE from a sub-chain of table references + + @param tables Points to the beginning of the sub-chain + @param tables_last Points to the address with the sub-chain barrier + + @details + The method resolves tables references to CTE from the chain of + table references specified by the parameters 'tables' and 'tables_last'. + It resolves the references against the CTE definition occurred in a query + or the specification of a CTE whose parsing tree is represented by + this LEX structure. The method is always called right after the process + of parsing the query or of the specification of a CTE has been finished, + thus the chain of table references used in the parsed fragment has been + already built. It is assumed that parameters of the method specify a + a sub-chain of this chain. + If a table reference can be potentially a table reference to a CTE and it + has not been resolved yet then the method tries to find the definition + of the CTE against which the reference can be resolved. If it succeeds + it sets the field TABLE_LIST::with to point to the found definition. + It also sets the field TABLE_LIST::derived to point to the specification + of the found CTE and sets TABLE::db.str to empty_c_string. This will + allow to handle this table reference like a reference to a derived handle. + If another table reference has been already resolved against this CTE + and this CTE is not recursive then a clone of the CTE specification is + constructed using the function With_element::clone_parsed_spec() and + TABLE_LIST::derived is set to point to this clone rather than to the + original specification. + If the method does not find a matched CTE definition in the parsed fragment + then in the case when the flag this->only_cte_resolution is set to true + it just moves to the resolution of the next table reference from the + specified sub-chain while in the case when this->only_cte_resolution is set + to false the method additionally sets an mdl request for this table + reference. + + @notes + The flag this->only_cte_resolution is set to true in the cases when + the failure to resolve a table reference as a CTE reference within + the fragment associated with this LEX structure does not imply that + this table reference cannot be resolved as such at all. + + @retval false On success: no errors reported, no memory allocations failed + @retval true Otherwise +*/ + +bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last) +{ + With_element *with_elem= 0; + + for (TABLE_LIST *tbl= tables; tbl != *tables_last; tbl= tbl->next_global) + { + if (tbl->derived) + continue; + if (!tbl->db.str && !tbl->with) + tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl); + if (!tbl->with) // no CTE matches table reference tbl + { + if (only_cte_resolution) + continue; + if (!tbl->db.str) // no database specified in table reference tbl + { + if (!thd->db.str) // no default database is set + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + return true; + } + if (copy_db_to(&tbl->db)) + return true; + if (!(tbl->table_options & TL_OPTION_ALIAS)) + tbl->mdl_request.init(MDL_key::TABLE, tbl->db.str, + tbl->table_name.str, + tbl->mdl_type, MDL_TRANSACTION); + tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); + } + continue; + } + with_elem= tbl->with; + if (tbl->is_recursive_with_table() && + !tbl->is_with_table_recursive_reference()) + { + tbl->with->rec_outer_references++; + while ((with_elem= with_elem->get_next_mutually_recursive()) != + tbl->with) + with_elem->rec_outer_references++; + } + if (!with_elem->is_used_in_query || with_elem->is_recursive) + { + tbl->derived= with_elem->spec; + if (tbl->derived != tbl->select_lex->master_unit() && + !with_elem->is_recursive && + !tbl->is_with_table_recursive_reference()) + { + tbl->derived->move_as_slave(tbl->select_lex); + } + with_elem->is_used_in_query= true; + } + else + { + if (!(tbl->derived= tbl->with->clone_parsed_spec(thd->lex, tbl))) + return true; + } + tbl->db.str= empty_c_string; + tbl->db.length= 0; + tbl->schema_table= 0; + if (tbl->derived) + { + tbl->derived->first_select()->set_linkage(DERIVED_TABLE_TYPE); + tbl->select_lex->add_statistics(tbl->derived); + } + if (tbl->with->is_recursive && tbl->is_with_table_recursive_reference()) + continue; + with_elem->inc_references(); + } + return false; +} + + +/** + @brief + Find out dependencies between CTEs, resolve references to them + + @details + The function can be called in two modes. With this->with_cte_resolution + set to false the function only finds out all dependencies between CTEs + used in a query expression with a WITH clause whose parsing has been + just finished. Based on these dependencies recursive CTEs are detected. + If this->with_cte_resolution is set to true the function additionally + resolves all references to CTE occurred in this query expression. + + @retval + true on failure + false on success +*/ + +bool +LEX::check_cte_dependencies_and_resolve_references() +{ + if (check_dependencies_in_with_clauses()) + return true; + if (!with_cte_resolution) + return false; + if (resolve_references_to_cte(query_tables, query_tables_last)) + return true; + if (resolve_references_to_cte_in_hanging_cte()) + return true; + return false; +} + + /** @brief Check dependencies between tables defined in this with clause @@ -137,10 +332,11 @@ bool With_clause::check_dependencies() elem != with_elem; elem= elem->next) { - if (lex_string_cmp(system_charset_info, with_elem->query_name, - elem->query_name) == 0) + if (lex_string_cmp(system_charset_info, with_elem->get_name(), + elem->get_name()) == 0) { - my_error(ER_DUP_QUERY_NAME, MYF(0), with_elem->query_name->str); + my_error(ER_DUP_QUERY_NAME, MYF(0), + with_elem->get_name_str()); return true; } } @@ -247,13 +443,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table, with_elem != barrier; with_elem= with_elem->next) { - if (my_strcasecmp(system_charset_info, with_elem->query_name->str, - table->table_name.str) == 0 && + if (my_strcasecmp(system_charset_info, with_elem->get_name_str(), + table->table_name.str) == 0 && !table->is_fqtn) { table->set_derived(); - table->db.str= empty_c_string; - table->db.length= 0; + with_elem->referenced= true; return with_elem; } } @@ -610,7 +805,7 @@ bool With_clause::check_anchors() if (elem == with_elem) { my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -643,7 +838,7 @@ bool With_clause::check_anchors() if (elem->work_dep_map & elem->get_elem_map()) { my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -797,7 +992,8 @@ bool With_element::set_unparsed_spec(THD *thd, @brief Create a clone of the specification for the given with table - @param thd The context of the statement containing this with element + @param old_lex The LEX structure created for the query or CTE specification + where this With_element is defined @param with_table The reference to the table defined in this element for which the clone is created. @@ -807,12 +1003,13 @@ bool With_element::set_unparsed_spec(THD *thd, this element. The clone is created when the string with the specification saved in unparsed_spec is fed into the parser as an input string. The parsing - this string a unit object representing the specification is build. + this string a unit object representing the specification is built. A chain of all table references occurred in the specification is also formed. The method includes the new unit and its sub-unit into hierarchy of the units of the main query. I also insert the constructed chain of the table references into the chain of all table references of the main query. + The method resolves all references to CTE in the clone. @note Clones is created only for not first references to tables defined in @@ -828,114 +1025,128 @@ bool With_element::set_unparsed_spec(THD *thd, NULL - otherwise */ -st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, +st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table) { + THD *thd= old_lex->thd; LEX *lex; - st_select_lex_unit *res= NULL; - Query_arena backup; - Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup); + st_select_lex_unit *res= NULL; if (!(lex= (LEX*) new(thd->mem_root) st_lex_local)) - { - if (arena) - thd->restore_active_arena(arena, &backup); return res; - } - LEX *old_lex= thd->lex; thd->lex= lex; bool parse_status= false; - Parser_state parser_state; - TABLE_LIST *spec_tables; - TABLE_LIST *spec_tables_tail; st_select_lex *with_select; char save_end= unparsed_spec.str[unparsed_spec.length]; ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0'; - if (parser_state.init(thd, (char*) unparsed_spec.str, (unsigned int)unparsed_spec.length)) - goto err; - parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; - parser_state.m_lip.multi_statements= false; - parser_state.m_lip.m_digest= NULL; lex_start(thd); lex->clone_spec_offset= unparsed_spec_offset; - lex->param_list= old_lex->param_list; - lex->sphead= old_lex->sphead; - lex->spname= old_lex->spname; - lex->spcont= old_lex->spcont; - lex->sp_chistics= old_lex->sp_chistics; - - lex->stmt_lex= old_lex; - parse_status= parse_sql(thd, &parser_state, 0); + lex->with_cte_resolution= true; + + /* + The specification of a CTE is to be parsed as a regular query. + At the very end of the parsing query the function + check_cte_dependencies_and_resolve_references() will be called. + It will check the dependencies between CTEs that are defined + within the query and will resolve CTE references in this query. + If a table reference is not resolved as a CTE reference within + this query it still can be resolved as a reference to a CTE defined + in the same clause as the CTE whose specification is to be parsed + or defined in an embedding CTE definition. + + Example: + with + cte1 as ( ... ), + cte2 as ([WITH ...] select ... from cte1 ...) + select ... from cte2 as r, ..., cte2 as s ... + + Here the specification of cte2 has be cloned for table reference + with alias s1. The specification contains a reference to cte1 + that is defined outside this specification. If the reference to + cte1 cannot be resolved within the specification of cte2 it's + not necessarily has to be a reference to a non-CTE table. That's + why the flag lex->only_cte_resolution has to be set to true + before parsing of the specification of cte2 invoked by this + function starts. Otherwise an mdl_lock would be requested for s + and this would not be correct. + */ + + lex->only_cte_resolution= true; + + lex->stmt_lex= old_lex->stmt_lex ? old_lex->stmt_lex : old_lex; + + parse_status= thd->sql_parser(old_lex, lex, + (char*) unparsed_spec.str, + (unsigned int)unparsed_spec.length, + stmt_prepare_mode); + ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= save_end; - with_select= lex->first_select_lex(); + with_select= lex->unit.first_select(); if (parse_status) goto err; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - goto err; - - spec_tables= lex->query_tables; - spec_tables_tail= 0; - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) - { - if (!tbl->derived && !tbl->schema_table && - thd->open_temporary_table(tbl)) - goto err; - spec_tables_tail= tbl; - } - if (spec_tables) + /* + The global chain of TABLE_LIST objects created for the specification that + just has been parsed is added to such chain that contains the reference + to the CTE whose specification is parsed right after the TABLE_LIST object + created for the reference. + */ + if (lex->query_tables) { - if (with_table->next_global) + head->tables_pos.set_start_pos(&with_table->next_global); + head->tables_pos.set_end_pos(lex->query_tables_last); + TABLE_LIST *next_tbl= with_table->next_global; + if (next_tbl) { - spec_tables_tail->next_global= with_table->next_global; - with_table->next_global->prev_global= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= next_tbl->prev_global)= + lex->query_tables; + *(next_tbl->prev_global= lex->query_tables_last)= next_tbl; } else { - old_lex->query_tables_last= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= old_lex->query_tables_last)= + lex->query_tables; + old_lex->query_tables_last= lex->query_tables_last; } - spec_tables->prev_global= &with_table->next_global; - with_table->next_global= spec_tables; } res= &lex->unit; res->with_element= this; + /* + The unit of the specification that just has been parsed is included + as a slave of the select that contained in its from list the table + reference for which the unit has been created. + */ lex->unit.include_down(with_table->select_lex); - lex->unit.set_slave(with_select); + lex->unit.set_slave(with_select); + lex->unit.cloned_from= spec; old_lex->all_selects_list= (st_select_lex*) (lex->all_selects_list-> insert_chain_before( (st_select_lex_node **) &(old_lex->all_selects_list), with_select)); - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - res= NULL; + /* - Resolve references to CTE from the spec_tables list that has not - been resolved yet. + Now all references to the CTE defined outside of the cloned specification + has to be resolved. Additionally if old_lex->only_cte_resolution == false + for the table references that has not been resolved requests for mdl_locks + has to be set. */ - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) + lex->only_cte_resolution= old_lex->only_cte_resolution; + if (lex->resolve_references_to_cte(lex->query_tables, + lex->query_tables_last)) { - if (!tbl->with) - tbl->with= with_select->find_table_def_in_with_clauses(tbl); - if (tbl == spec_tables_tail) - break; - } - if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) + res= NULL; goto err; + } - lex->sphead= NULL; // in order not to delete lex->sphead + lex->sphead= NULL; // in order not to delete lex->sphead lex_end(lex); err: - if (arena) - thd->restore_active_arena(arena, &backup); thd->lex= old_lex; return res; } @@ -1105,59 +1316,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table) } -/** - @brief - Set the specifying unit in this reference to a with table - - @details - The method assumes that the given element with_elem defines the table T - this table reference refers to. - If this is the first reference to T the method just sets its specification - in the field 'derived' as the unit that yields T. Otherwise the method - first creates a clone specification and sets rather this clone in this field. - - @retval - false on success - true on failure -*/ - -bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem) -{ - if (table) - { - /* - This table was prematurely identified as a temporary table. - We correct it here, but it's not a nice solution in the case - when the temporary table with this name is not used anywhere - else in the query. - */ - thd->mark_tmp_table_as_free_for_reuse(table); - table= 0; - } - with= with_elem; - schema_table= NULL; - if (!with_elem->is_referenced() || with_elem->is_recursive) - { - derived= with_elem->spec; - if (derived != select_lex->master_unit() && - !with_elem->is_recursive && - !is_with_table_recursive_reference()) - { - derived->move_as_slave(select_lex); - } - } - else - { - if(!(derived= with_elem->clone_parsed_spec(thd, this))) - return true; - } - derived->first_select()->set_linkage(DERIVED_TABLE_TYPE); - select_lex->add_statistics(derived); - with_elem->inc_references(); - return false; -} - - bool TABLE_LIST::is_recursive_with_table() { return with && with->is_recursive; @@ -1257,7 +1415,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant) if (only_standard_compliant && with_elem->is_unrestricted()) { my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE, - MYF(0), with_elem->query_name->str); + MYF(0), with_elem->get_name_str()); return true; } @@ -1457,7 +1615,7 @@ void With_clause::print(String *str, enum_query_type query_type) void With_element::print(String *str, enum_query_type query_type) { - str->append(query_name); + str->append(get_name()); if (column_list.elements) { List_iterator_fast li(column_list); diff --git a/sql/sql_cte.h b/sql/sql_cte.h index 80d56644d7e..4693599300b 100644 --- a/sql/sql_cte.h +++ b/sql/sql_cte.h @@ -24,6 +24,39 @@ class select_unit; struct st_unit_ctxt_elem; +/** + @class With_element_head + @brief Head of the definition of a CTE table + + It contains the name of the CTE and it contains the position of the subchain + of table references used in the definition in the global chain of table + references used in the query where this definition is encountered. +*/ + +class With_element_head : public Sql_alloc +{ + /* The name of the defined CTE */ + LEX_CSTRING *query_name; + +public: + /* + The structure describing the subchain of the table references used in + the specification of the defined CTE in the global chain of table + references used in the query. The structure is fully defined only + after the CTE definition has been parsed. + */ + TABLE_CHAIN tables_pos; + + With_element_head(LEX_CSTRING *name) + : query_name(name) + { + tables_pos.set_start_pos(0); + tables_pos.set_end_pos(0); + } + friend class With_element; +}; + + /** @class With_element @brief Definition of a CTE table @@ -85,9 +118,22 @@ private: subqueries and specifications of other with elements). */ uint references; + + /* + true <=> this With_element is referred in the query in which the + element is defined + */ + bool referenced; + + /* + true <=> this With_element is needed for the execution of the query + in which the element is defined + */ + bool is_used_in_query; + /* Unparsed specification of the query that specifies this element. - It used to build clones of the specification if they are needed. + It's used to build clones of the specification if they are needed. */ LEX_CSTRING unparsed_spec; /* Offset of the specification in the input string */ @@ -101,10 +147,12 @@ private: public: /* - The name of the table introduced by this with elememt. The name - can be used in FROM lists of the queries in the scope of the element. + Contains the name of the defined With element and the position of + the subchain of the tables references used by its definition in the + global chain of TABLE_LIST objects created for the whole query. */ - LEX_CSTRING *query_name; + With_element_head *head; + /* Optional list of column names to name the columns of the table introduced by this with element. It is used in the case when the names are not @@ -162,18 +210,27 @@ public: /* List of derived tables containing recursive references to this CTE */ SQL_I_List derived_with_rec_ref; - With_element(LEX_CSTRING *name, + With_element(With_element_head *h, List list, st_select_lex_unit *unit) : next(NULL), base_dep_map(0), derived_dep_map(0), sq_dep_map(0), work_dep_map(0), mutually_recursive(0), top_level_dep_map(0), sq_rec_ref(NULL), next_mutually_recursive(NULL), references(0), - query_name(name), column_list(list), spec(unit), + referenced(false), is_used_in_query(false), + head(h), column_list(list), spec(unit), is_recursive(false), rec_outer_references(0), with_anchor(false), level(0), rec_result(NULL) { unit->with_element= this; } + LEX_CSTRING *get_name() { return head->query_name; } + const char *get_name_str() { return get_name()->str; } + + void set_tables_start_pos(TABLE_LIST **pos) + { head->tables_pos.set_start_pos(pos); } + void set_tables_end_pos(TABLE_LIST **pos) + { head->tables_pos.set_end_pos(pos); } + bool check_dependencies_in_spec(); void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt, @@ -200,9 +257,9 @@ public: bool set_unparsed_spec(THD *thd, const char *spec_start, const char *spec_end, my_ptrdiff_t spec_offset); - st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table); + st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table); - bool is_referenced() { return references != 0; } + bool is_referenced() { return referenced; } void inc_references() { references++; } @@ -260,6 +317,12 @@ public: void prepare_for_next_iteration(); friend class With_clause; + + friend + bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8; @@ -358,8 +421,10 @@ public: friend class With_element; friend - bool - check_dependencies_in_with_clauses(With_clause *with_clauses_list); + bool LEX::check_dependencies_in_with_clauses(); + + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; inline diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 386d179322e..c63a3b5273c 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -721,6 +721,8 @@ void LEX::start(THD *thd_arg) explain_json= false; context_analysis_only= 0; derived_tables= 0; + with_cte_resolution= false; + only_cte_resolution= false; safe_to_cache_query= 1; parsing_options.reset(); empty_field_list_on_rset= 0; @@ -2374,6 +2376,7 @@ void st_select_lex_unit::init_query() is_view= false; with_clause= 0; with_element= 0; + cloned_from= 0; columns_are_renamed= false; intersect_mark= NULL; with_wrapped_tvc= false; @@ -8326,6 +8329,8 @@ bool LEX::check_main_unit_semantics() if (unit.set_nest_level(0) || unit.check_parameters(first_select_lex())) return TRUE; + if (check_cte_dependencies_and_resolve_references()) + return TRUE; return FALSE; } @@ -9021,8 +9026,8 @@ void st_select_lex::add_statistics(SELECT_LEX_UNIT *unit) bool LEX::main_select_push(bool service) { DBUG_ENTER("LEX::main_select_push"); - current_select_number= 1; - builtin_select.select_number= 1; + current_select_number= ++thd->lex->stmt_lex->current_select_number; + builtin_select.select_number= current_select_number; builtin_select.is_service_select= service; if (push_select(&builtin_select)) DBUG_RETURN(TRUE); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2389b23e08e..eb21419c7af 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -917,6 +917,8 @@ public: With_clause *with_clause; /* With element where this unit is used as the specification (if any) */ With_element *with_element; + /* The unit used as a CTE specification from which this unit is cloned */ + st_select_lex_unit *cloned_from; /* thread handler */ THD *thd; /* @@ -1501,7 +1503,9 @@ public: } With_element *get_with_element() { - return master_unit()->with_element; + return master_unit()->cloned_from ? + master_unit()->cloned_from->with_element : + master_unit()->with_element; } With_element *find_table_def_in_with_clauses(TABLE_LIST *table); bool check_unrestricted_recursive(bool only_standard_compliant); @@ -3324,6 +3328,20 @@ public: */ uint8 derived_tables; uint8 context_analysis_only; + /* + true <=> The parsed fragment requires resolution of references to CTE + at the end of parsing. This name resolution process involves searching + for possible dependencies between CTE defined in the parsed fragment and + detecting possible recursive references. + The flag is set to true if the fragment contains CTE definitions. + */ + bool with_cte_resolution; + /* + true <=> only resolution of references to CTE are required in the parsed + fragment, no checking of dependencies between CTE is required. + This flag is used only when parsing clones of CTE specifications. + */ + bool only_cte_resolution; bool local_file; bool check_exists; bool autocommit; @@ -4571,6 +4589,11 @@ public: select_stack[0]->is_service_select); } + bool check_dependencies_in_with_clauses(); + bool resolve_references_to_cte_in_hanging_cte(); + bool check_cte_dependencies_and_resolve_references(); + bool resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3ae7c7c7df3..82b81a2bf4e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3554,9 +3554,6 @@ mysql_execute_command(THD *thd) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); } - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - DBUG_RETURN(1); - #ifdef HAVE_REPLICATION if (unlikely(thd->slave_thread)) { @@ -8187,7 +8184,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->is_fqtn= TRUE; ptr->db= table->db; } - else if (lex->copy_db_to(&ptr->db)) + else if (!lex->with_cte_resolution && lex->copy_db_to(&ptr->db)) DBUG_RETURN(0); else ptr->is_fqtn= FALSE; @@ -8204,7 +8201,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, } ptr->table_name= table->table; - ptr->lock_type= lock_type; + ptr->lock_type= lock_type; + ptr->mdl_type= mdl_type; + ptr->table_options= table_options; ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING); /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */ ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX); @@ -8886,8 +8885,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update { tables->lock_type= lock_type; tables->updating= for_update; - tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + + if (tables->db.str && tables->db.str[0]) + tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } DBUG_VOID_RETURN; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 8d094a09165..816990fc3b6 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2376,9 +2376,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) if (tables) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto error; - if (sql_command_flags[sql_command] & CF_HA_CLOSE) mysql_ha_rm_tables(thd, tables); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 126db90656c..1e3c4ce2a94 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -431,12 +431,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_type= OT_BASE_ONLY; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - { - res= TRUE; - goto err; - } - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* @@ -1413,9 +1407,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, TABLE_LIST *tbl; Security_context *security_ctx= 0; - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto err; - /* Check rights to run commands which show underlying tables. In the optimizer trace we would not like to show trace for diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9b2355e53c5..dfe239c41bd 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -770,6 +770,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) class sp_head *sphead; class sp_name *spname; class sp_variable *spvar; + class With_element_head *with_element_head; class With_clause *with_clause; class Virtual_column_info *virtual_column; @@ -2188,7 +2189,7 @@ END_OF_INPUT %type with_clause -%type query_name +%type with_element_head %type opt_with_column_list @@ -3336,7 +3337,11 @@ call: if (unlikely(Lex->call_statement_start(thd, $2))) MYSQL_YYABORT; } - opt_sp_cparam_list {} + opt_sp_cparam_list + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; /* CALL parameters */ @@ -4185,6 +4190,8 @@ sp_proc_stmt_return: LEX *lex= Lex; sp_head *sp= lex->sphead; Lex->pop_select(); //main select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont, $3, lex)) || unlikely(sp->restore_lex(thd))) @@ -13337,6 +13344,8 @@ do: { Lex->insert_list= $3; Lex->pop_select(); //main select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -15507,6 +15516,7 @@ with_clause: if (unlikely(with_clause == NULL)) MYSQL_YYABORT; lex->derived_tables|= DERIVED_WITH; + lex->with_cte_resolution= true; lex->curr_with_clause= with_clause; with_clause->add_to_list(Lex->with_clauses_list_last_next); if (lex->current_select && @@ -15534,7 +15544,7 @@ with_list: with_list_element: - query_name + with_element_head opt_with_column_list { $2= new List (Lex->with_column_list); @@ -15554,6 +15564,7 @@ with_list_element: if (elem->set_unparsed_spec(thd, spec_start, $7.pos(), spec_start - query_start)) MYSQL_YYABORT; + elem->set_tables_end_pos(lex->query_tables_last); } ; @@ -15580,12 +15591,15 @@ with_column_list: ; -query_name: +with_element_head: ident { - $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); - if (unlikely($$ == NULL)) + LEX_CSTRING *name= + (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); + $$= new (thd->mem_root) With_element_head(name); + if (unlikely(name == NULL || $$ == NULL)) MYSQL_YYABORT; + $$->tables_pos.set_start_pos(Lex->query_tables_last); } ; diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index cb58c4aff43..76eb21a88e8 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -248,6 +248,7 @@ void ORAerror(THD *thd, const char *s) class sp_head *sphead; class sp_name *spname; class sp_variable *spvar; + class With_element_head *with_element_head; class With_clause *with_clause; class Virtual_column_info *virtual_column; @@ -1689,7 +1690,7 @@ END_OF_INPUT %type with_clause -%type query_name +%type with_element_head %type opt_with_column_list @@ -3138,7 +3139,11 @@ call: if (unlikely(Lex->call_statement_start(thd, $2))) MYSQL_YYABORT; } - opt_sp_cparam_list {} + opt_sp_cparam_list + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; /* CALL parameters */ @@ -4092,6 +4097,8 @@ sp_proc_stmt_return: LEX *lex= Lex; sp_head *sp= lex->sphead; Lex->pop_select(); //main select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; if (unlikely(sp->m_handler->add_instr_freturn(thd, sp, lex->spcont, $3, lex)) || unlikely(sp->restore_lex(thd))) @@ -13436,6 +13443,8 @@ do: { Lex->insert_list= $3; Lex->pop_select(); //main select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -15624,6 +15633,7 @@ with_clause: if (unlikely(with_clause == NULL)) MYSQL_YYABORT; lex->derived_tables|= DERIVED_WITH; + lex->with_cte_resolution= true; lex->curr_with_clause= with_clause; with_clause->add_to_list(Lex->with_clauses_list_last_next); if (lex->current_select && @@ -15651,7 +15661,7 @@ with_list: with_list_element: - query_name + with_element_head opt_with_column_list { $2= new List (Lex->with_column_list); @@ -15671,6 +15681,7 @@ with_list_element: if (elem->set_unparsed_spec(thd, spec_start, $7.pos(), spec_start - query_start)) MYSQL_YYABORT; + elem->set_tables_end_pos(lex->query_tables_last); } ; @@ -15697,12 +15708,15 @@ with_column_list: ; -query_name: +with_element_head: ident { - $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); - if (unlikely($$ == NULL)) + LEX_CSTRING *name= + (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); + $$= new (thd->mem_root) With_element_head(name); + if (unlikely(name == NULL || $$ == NULL)) MYSQL_YYABORT; + $$->tables_pos.set_start_pos(Lex->query_tables_last); } ; diff --git a/sql/table.h b/sql/table.h index 4a739ed1f9f..f510aaa6968 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2139,6 +2139,29 @@ struct vers_select_conds_t struct LEX; class Index_hint; + +/* + @struct TABLE_CHAIN + @brief Subchain of global chain of table references + + The structure contains a pointer to the address of the next_global + pointer to the first TABLE_LIST objectof the subchain and the address + of the next_global pointer to the element right after the last + TABLE_LIST object of the subchain. For an empty subchain both pointers + have the same value. +*/ + +struct TABLE_CHAIN +{ + TABLE_CHAIN() {} + + TABLE_LIST **start_pos; + TABLE_LIST ** end_pos; + + void set_start_pos(TABLE_LIST **pos) { start_pos= pos; } + void set_end_pos(TABLE_LIST **pos) { end_pos= pos; } +}; + struct TABLE_LIST { TABLE_LIST() {} /* Remove gcc warning */ @@ -2473,6 +2496,20 @@ struct TABLE_LIST /* call back function for asking handler about caching in query cache */ qc_engine_callback callback_func; thr_lock_type lock_type; + + /* + Two fields below are set during parsing this table reference in the cases + when the table reference can be potentially a reference to a CTE table. + In this cases the fact that the reference is a reference to a CTE or not + will be ascertained at the very end of parsing of the query when referencies + to CTE are resolved. For references to CTE and to derived tables no mdl + requests are needed while for other table references they are. If a request + is possibly postponed the info that allows to issue this request must be + saved in 'mdl_type' and 'table_options'. + */ + enum_mdl_type mdl_type; + ulong table_options; + uint outer_join; /* Which join type */ uint shared; /* Used in multi-upd */ bool updatable; /* VIEW/TABLE can be updated now */ -- cgit v1.2.1 From 6d549aecf5256b0664a33482481627d0dd30a80d Mon Sep 17 00:00:00 2001 From: nia Date: Tue, 25 May 2021 13:34:52 +0200 Subject: threadpool_generic: support future NetBSD kqueue versions In NetBSD 9.x and prior, udata is an intptr_t, but in 10.x (current development branch) it was changed to be a void * for compatibility with other BSDs a year or so ago. Unfortunately, this does not simplify the code, as NetBSD 8.x and 9.x are still supported and will be for a few more years. Signed-off-by: Nia Alarie --- sql/threadpool_generic.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sql/threadpool_generic.cc b/sql/threadpool_generic.cc index b6bb47e8f29..f59c7cbea9d 100644 --- a/sql/threadpool_generic.cc +++ b/sql/threadpool_generic.cc @@ -234,14 +234,19 @@ static void *native_event_get_userdata(native_event *event) #elif defined(HAVE_KQUEUE) /* - NetBSD is incompatible with other BSDs , last parameter in EV_SET macro - (udata, user data) needs to be intptr_t, whereas it needs to be void* - everywhere else. + NetBSD prior to 9.99.17 is incompatible with other BSDs, last parameter + in EV_SET macro (udata, user data) needs to be intptr_t, whereas it needs + to be void* everywhere else. */ #ifdef __NetBSD__ +#include +# if !__NetBSD_Prereq__(9,99,17) #define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, (intptr_t)g) -#else +# endif +#endif + +#ifndef MY_EV_SET #define MY_EV_SET(a, b, c, d, e, f, g) EV_SET(a, b, c, d, e, f, g) #endif -- cgit v1.2.1 From 4926498a67794ff27d1dd5795ce8b75e09818ed8 Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Fri, 7 May 2021 01:33:27 -0400 Subject: CRC32 on OpenBSD/powerpc64. closes #1828 --- mysys/crc32/crc32c.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysys/crc32/crc32c.cc b/mysys/crc32/crc32c.cc index 082d467e7da..b48e744a663 100644 --- a/mysys/crc32/crc32c.cc +++ b/mysys/crc32/crc32c.cc @@ -517,12 +517,12 @@ static int arch_ppc_probe(void) { return arch_ppc_crc32; } -#elif _AIX +#elif defined(_AIX) || defined(__OpenBSD__) static int arch_ppc_probe(void) { arch_ppc_crc32 = 0; #if defined(__powerpc64__) - // AIX 7.1+ has vector crypto features on all POWER 8+ + // AIX 7.1+/OpenBSD has vector crypto features on all POWER 8+ arch_ppc_crc32 = 1; #endif /* __powerpc64__ */ -- cgit v1.2.1 From 675716e1cb34e29f7d3f46aac9a93adf02ac0851 Mon Sep 17 00:00:00 2001 From: Igor Babaev Date: Tue, 25 May 2021 17:13:17 -0700 Subject: MDEV-23886 Reusing CTE inside a function fails with table doesn't exist In the code existed just before this patch binding of a table reference to the specification of the corresponding CTE happens in the function open_and_process_table(). If the table reference is not the first in the query the specification is cloned in the same way as the specification of a view is cloned for any reference of the view. This works fine for standalone queries, but does not work for stored procedures / functions for the following reason. When the first call of a stored procedure/ function SP is processed the body of SP is parsed. When a query of SP is parsed the info on each encountered table reference is put into a TABLE_LIST object linked into a global chain associated with the query. When parsing of the query is finished the basic info on the table references from this chain except table references to derived tables and information schema tables is put in one hash table associated with SP. When parsing of the body of SP is finished this hash table is used to construct TABLE_LIST objects for all table references mentioned in SP and link them into the list of such objects passed to a pre-locking process that calls open_and_process_table() for each table from the list. When a TABLE_LIST for a view is encountered the view is opened and its specification is parsed. For any table reference occurred in the specification a new TABLE_LIST object is created to be included into the list for pre-locking. After all objects in the pre-locking have been looked through the tables mentioned in the list are locked. Note that the objects referenced CTEs are just skipped here as it is impossible to resolve these references without any info on the context where they occur. Now the statements from the body of SP are executed one by one that. At the very beginning of the execution of a query the tables used in the query are opened and open_and_process_table() now is called for each table reference mentioned in the list of TABLE_LIST objects associated with the query that was built when the query was parsed. For each table reference first the reference is checked against CTEs definitions in whose scope it occurred. If such definition is found the reference is considered resolved and if this is not the first reference to the found CTE the the specification of the CTE is re-parsed and the result of the parsing is added to the parsing tree of the query as a sub-tree. If this sub-tree contains table references to other tables they are added to the list of TABLE_LIST objects associated with the query in order the referenced tables to be opened. When the procedure that opens the tables comes to the TABLE_LIST object created for a non-first reference to a CTE it discovers that the referenced table instance is not locked and reports an error. Thus processing non-first table references to a CTE similar to how references to view are processed does not work for queries used in stored procedures / functions. And the main problem is that the current pre-locking mechanism employed for stored procedures / functions does not allow to save the context in which a CTE reference occur. It's not trivial to save the info about the context where a CTE reference occurs while the resolution of the table reference cannot be done without this context and consequentially the specification for the table reference cannot be determined. This patch solves the above problem by moving resolution of all CTE references at the parsing stage. More exactly references to CTEs occurred in a query are resolved right after parsing of the query has finished. After resolution any CTE reference it is marked as a reference to to derived table. So it is excluded from the hash table created for pre-locking used base tables and view when the first call of a stored procedure / function is processed. This solution required recursive calls of the parser. The function THD::sql_parser() has been added specifically for recursive invocations of the parser. # Conflicts: # sql/sql_cte.cc # sql/sql_cte.h # sql/sql_lex.cc # sql/sql_lex.h # sql/sql_view.cc # sql/sql_yacc.yy # sql/sql_yacc_ora.yy --- mysql-test/main/cte_nonrecursive.result | 201 +++++++++++++++ mysql-test/main/cte_nonrecursive.test | 202 +++++++++++++++ sql/item_subselect.cc | 1 - sql/sp_head.cc | 3 +- sql/sql_base.cc | 33 +-- sql/sql_class.cc | 56 +++++ sql/sql_class.h | 8 +- sql/sql_cte.cc | 422 ++++++++++++++++++++++---------- sql/sql_cte.h | 85 ++++++- sql/sql_lex.cc | 9 +- sql/sql_lex.h | 26 +- sql/sql_parse.cc | 15 +- sql/sql_prepare.cc | 3 - sql/sql_view.cc | 9 - sql/sql_yacc.yy | 26 +- sql/table.h | 37 +++ 16 files changed, 929 insertions(+), 207 deletions(-) diff --git a/mysql-test/main/cte_nonrecursive.result b/mysql-test/main/cte_nonrecursive.result index 54283f1ccf1..763e51f61cb 100644 --- a/mysql-test/main/cte_nonrecursive.result +++ b/mysql-test/main/cte_nonrecursive.result @@ -1763,6 +1763,207 @@ a c 2 1 7 3 drop table t1; +# +# MDEV-23886: Stored Function returning the result of a query +# that uses CTE over a table twice +# +create table t1 (c1 int); +insert into t1 values (1),(2),(6); +create function f1() returns int return +( with cte1 as (select c1 from t1) +select sum(c1) from +(select * from cte1 union all select * from cte1) dt +); +select f1(); +f1() +18 +create function f2() returns int return +( with cte1 as (select c1 from t1) +select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); +f2() +9 +create function f3() returns int return +( with cte1 as (select c1 from t1) +select +case +when exists(select 1 from cte1 where c1 between 1 and 2) then 1 +when exists(select 1 from cte1 where c1 between 5 and 6) then 2 +else 0 +end +); +select f3(); +f3() +1 +create view v1 as (select c1 from t1); +create function f4() returns int return +( select sum(c1) from +(select * from v1 union all select * from v1) dt +); +select f4(); +f4() +18 +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); +f5() +9 +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; +create function f6() returns int return +(select s from v2); +select f6(); +f6() +18 +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); +f7() +18 +select f5() + f6(); +f5() + f6() +27 +prepare stmt from "select f5() + f6();"; +execute stmt; +f5() + f6() +27 +execute stmt; +f5() + f6() +27 +deallocate prepare stmt; +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; +drop view v1; +drop view v2; +create table t2 (a int, b int); +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; +select * from t2; +a b +6 6 +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +drop procedure p1; +# checking CTE resolution for queries with hanging CTEs +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; +a b +1 2 +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +a b +6 6 +2 2 +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; +ERROR 42S22: Unknown column 'c1' in 'where clause' +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; +ERROR 42S22: Unknown column 'cte2.c1' in 'where clause' +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; +a b +1 1 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; +a b +6 6 +2 2 +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; +ERROR 23000: Column 'c1' in where clause is ambiguous +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; +a b +1 1 +2 1 +1 2 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; +a b +6 6 +2 2 +with cte3 as +( with cte2(a,b) as +( with cte1 as (select * from t1 where c1 <= 2) +select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; +ERROR 42S22: Unknown column 'r1.c1' in 'field list' +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +select * from t2; +a b +6 6 +2 2 +2 2 +drop procedure p1; +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | +call p1(); +ERROR 42S22: Unknown column 'a' in 'field list' +drop procedure p1; +drop table t1,t2; # End of 10.2 tests # # MDEV-21673: several references to CTE that uses diff --git a/mysql-test/main/cte_nonrecursive.test b/mysql-test/main/cte_nonrecursive.test index a4a1a1784cd..59dae44cb66 100644 --- a/mysql-test/main/cte_nonrecursive.test +++ b/mysql-test/main/cte_nonrecursive.test @@ -1273,6 +1273,208 @@ select a, c from cte as r2 where a > 4; drop table t1; +--echo # +--echo # MDEV-23886: Stored Function returning the result of a query +--echo # that uses CTE over a table twice +--echo # + +create table t1 (c1 int); +insert into t1 values (1),(2),(6); + +create function f1() returns int return +( with cte1 as (select c1 from t1) + select sum(c1) from + (select * from cte1 union all select * from cte1) dt +); +select f1(); + +create function f2() returns int return +( with cte1 as (select c1 from t1) + select sum(s.c1) from cte1 as s, cte1 as t where s.c1=t.c1 +); +select f2(); + +create function f3() returns int return +( with cte1 as (select c1 from t1) + select + case + when exists(select 1 from cte1 where c1 between 1 and 2) then 1 + when exists(select 1 from cte1 where c1 between 5 and 6) then 2 + else 0 + end +); +select f3(); + +create view v1 as (select c1 from t1); + +create function f4() returns int return +( select sum(c1) from + (select * from v1 union all select * from v1) dt +); +select f4(); + +create function f5() returns int return +( select sum(s.c1) from v1 as s, v1 as t where s.c1=t.c1 +); +select f5(); + +create view v2(s) as +with cte1 as (select c1 from t1) +select sum(c1) from (select * from cte1 union all select * from cte1) dt; + +create function f6() returns int return +(select s from v2); +select f6(); + +create function f7() returns int return +( select r.s from v2 as r, v2 as t where r.s=t.s +); +select f7(); + +select f5() + f6(); + +prepare stmt from "select f5() + f6();"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +drop function f1; +drop function f2; +drop function f3; +drop function f4; +drop function f5; +drop function f6; +drop function f7; + +drop view v1; +drop view v2; + +create table t2 (a int, b int); + +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 > 5; + +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from cte1 as s, cte1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +--echo # checking CTE resolution for queries with hanging CTEs + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from cte3; + +select * from t2; + +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where c1 >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.b) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with +cte1(a) as (select * from t1 where c1 <= 2), +cte2(b) as (select * from cte1 where a >= 2), +cte3 as (select * from cte1,cte2 where cte1.a < cte2.c1) +select * from t2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from cte2; + +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) +select * from t2; + +--error ER_NON_UNIQ_ERROR +with +cte1 as (select * from t1 where c1 <= 2), +cte2(a,b) as (select * from cte1 as s1, cte1 as s2 where s1.c1=c1) +select * from t2; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from cte3; + +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.a,r2.b from cte2 as r1, cte2 as r2) +select * from t2; + +--error ER_BAD_FIELD_ERROR +with cte3 as +( with cte2(a,b) as + ( with cte1 as (select * from t1 where c1 <= 2) + select * from cte1 as s1, cte1 as s2 where s1.c1=s2.c1) + select r1.c1,r2.c1 from cte2 as r1, cte2 as r2) +select * from t2; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select c1 from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +call p1(); +select * from t2; + +drop procedure p1; + +delimiter |; + +create procedure p1() +begin +insert into t2 +with cte1 as (select a from t1) +select * from t1 as s, t1 as t where s.c1=t.c1 and s.c1 <= 2 and t.c1 >= 2; +end | + +delimiter ;| + +--error ER_BAD_FIELD_ERROR +call p1(); + +drop procedure p1; + +drop table t1,t2; + --echo # End of 10.2 tests --echo # diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 7fc7f4139e9..ffebf7d6994 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1846,7 +1846,6 @@ double Item_in_subselect::val_real() As far as Item_in_subselect called only from Item_in_optimizer this method should not be used */ - DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); if (forced_const) return value; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 513e7207b7e..b8e8def69fe 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3487,8 +3487,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, Json_writer_object trace_command(thd); Json_writer_array trace_command_steps(thd, "steps"); if (open_tables) - res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) || - instr->exec_open_and_lock_tables(thd, m_lex->query_tables); + res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); if (likely(!res)) { diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e926ebc0a3a..f9f9856f4c0 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3589,7 +3589,11 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, if (tables->derived) { if (!tables->view) + { + if (!tables->is_derived()) + tables->set_derived(); goto end; + } /* We restore view's name and database wiped out by derived tables processing and fall back to standard open process in order to @@ -3599,35 +3603,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, tables->db= tables->view_db; tables->table_name= tables->view_name; } - else if (tables->select_lex) - { - /* - Check whether 'tables' refers to a table defined in a with clause. - If so set the reference to the definition in tables->with. - */ - if (!tables->with) - tables->with= tables->select_lex->find_table_def_in_with_clauses(tables); - /* - If 'tables' is defined in a with clause set the pointer to the - specification from its definition in tables->derived. - */ - if (tables->with) - { - if (tables->is_recursive_with_table() && - !tables->is_with_table_recursive_reference()) - { - tables->with->rec_outer_references++; - With_element *with_elem= tables->with; - while ((with_elem= with_elem->get_next_mutually_recursive()) != - tables->with) - with_elem->rec_outer_references++; - } - if (tables->set_as_with_table(thd, tables->with)) - DBUG_RETURN(1); - else - goto end; - } - } if (!tables->derived && is_infoschema_db(&tables->db)) { diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ac42b50b4a1..8b61750ffca 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2810,6 +2810,62 @@ void THD::close_active_vio() #endif +/* + @brief MySQL parser used for recursive invocations + + @param old_lex The LEX structure in the state when this parser + is called recursively + @param lex The LEX structure used to parse a new SQL fragment + @param str The SQL fragment to parse + @param str_len The length of the SQL fragment to parse + @param stmt_prepare_mode true <=> when parsing a prepare statement + + @details + This function is to be used when parsing of an SQL fragment is + needed within one of the grammar rules. + + @notes + Currently the function is used only when the specification of a CTE + is parsed for the not first and not recursive references of the CTE. + + @retval false On a successful parsing of the fragment + @retval true Otherwise +*/ + +bool THD::sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode) +{ + extern int MYSQLparse(THD * thd); + extern int ORAparse(THD * thd); + + bool parse_status= false; + Parser_state parser_state; + Parser_state *old_parser_state= m_parser_state; + + if (parser_state.init(this, str, str_len)) + return true; + + m_parser_state= &parser_state; + parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; + parser_state.m_lip.multi_statements= false; + parser_state.m_lip.m_digest= NULL; + + lex->param_list= old_lex->param_list; + lex->sphead= old_lex->sphead; + lex->spname= old_lex->spname; + lex->spcont= old_lex->spcont; + lex->sp_chistics= old_lex->sp_chistics; + lex->trg_chistics= old_lex->trg_chistics; + + parse_status= (variables.sql_mode & MODE_ORACLE) ? + ORAparse(this) : MYSQLparse(this) != 0; + + m_parser_state= old_parser_state; + + return parse_status; +} + + struct Item_change_record: public ilink { Item **place; diff --git a/sql/sql_class.h b/sql/sql_class.h index de05b0de384..ac8d26cce03 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4401,14 +4401,11 @@ public: to resolve all CTE names as we don't need this message to be thrown for any CTE references. */ - if (!lex->with_clauses_list) + if (!lex->with_cte_resolution) { my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); return TRUE; } - /* This will allow to throw an error later for non-CTE references */ - to->str= NULL; - to->length= 0; return FALSE; } @@ -5210,6 +5207,9 @@ public: Item *sp_prepare_func_item(Item **it_addr, uint cols= 1); bool sp_eval_expr(Field *result_field, Item **expr_item_ptr); + bool sql_parser(LEX *old_lex, LEX *lex, + char *str, uint str_len, bool stmt_prepare_mode); + }; diff --git a/sql/sql_cte.cc b/sql/sql_cte.cc index 815a0f43c17..1dbdfbd9b1f 100644 --- a/sql/sql_cte.cc +++ b/sql/sql_cte.cc @@ -84,7 +84,7 @@ void st_select_lex_unit::set_with_clause(With_clause *with_cl) true on failure */ -bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) +bool LEX::check_dependencies_in_with_clauses() { for (With_clause *with_clause= with_clauses_list; with_clause; @@ -100,6 +100,201 @@ bool check_dependencies_in_with_clauses(With_clause *with_clauses_list) } +/** + @brief + Resolve references to CTE in specification of hanging CTE + + @details + A CTE to which there are no references in the query is called hanging CTE. + Although such CTE is not used for execution its specification must be + subject to context analysis. All errors concerning references to + non-existing tables or fields occurred in the specification must be + reported as well as all other errors caught at the prepare stage. + The specification of a hanging CTE might contain references to other + CTE outside of the specification and within it if the specification + contains a with clause. This function resolves all such references for + all hanging CTEs encountered in the processed query. + + @retval + false on success + true on failure +*/ + +bool +LEX::resolve_references_to_cte_in_hanging_cte() +{ + for (With_clause *with_clause= with_clauses_list; + with_clause; with_clause= with_clause->next_with_clause) + { + for (With_element *with_elem= with_clause->with_list.first; + with_elem; with_elem= with_elem->next) + { + if (!with_elem->is_referenced()) + { + TABLE_LIST *first_tbl= + with_elem->spec->first_select()->table_list.first; + TABLE_LIST **with_elem_end_pos= with_elem->head->tables_pos.end_pos; + if (first_tbl && resolve_references_to_cte(first_tbl, with_elem_end_pos)) + return true; + } + } + } + return false; +} + + +/** + @brief + Resolve table references to CTE from a sub-chain of table references + + @param tables Points to the beginning of the sub-chain + @param tables_last Points to the address with the sub-chain barrier + + @details + The method resolves tables references to CTE from the chain of + table references specified by the parameters 'tables' and 'tables_last'. + It resolves the references against the CTE definition occurred in a query + or the specification of a CTE whose parsing tree is represented by + this LEX structure. The method is always called right after the process + of parsing the query or of the specification of a CTE has been finished, + thus the chain of table references used in the parsed fragment has been + already built. It is assumed that parameters of the method specify a + a sub-chain of this chain. + If a table reference can be potentially a table reference to a CTE and it + has not been resolved yet then the method tries to find the definition + of the CTE against which the reference can be resolved. If it succeeds + it sets the field TABLE_LIST::with to point to the found definition. + It also sets the field TABLE_LIST::derived to point to the specification + of the found CTE and sets TABLE::db.str to empty_c_string. This will + allow to handle this table reference like a reference to a derived handle. + If another table reference has been already resolved against this CTE + and this CTE is not recursive then a clone of the CTE specification is + constructed using the function With_element::clone_parsed_spec() and + TABLE_LIST::derived is set to point to this clone rather than to the + original specification. + If the method does not find a matched CTE definition in the parsed fragment + then in the case when the flag this->only_cte_resolution is set to true + it just moves to the resolution of the next table reference from the + specified sub-chain while in the case when this->only_cte_resolution is set + to false the method additionally sets an mdl request for this table + reference. + + @notes + The flag this->only_cte_resolution is set to true in the cases when + the failure to resolve a table reference as a CTE reference within + the fragment associated with this LEX structure does not imply that + this table reference cannot be resolved as such at all. + + @retval false On success: no errors reported, no memory allocations failed + @retval true Otherwise +*/ + +bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last) +{ + With_element *with_elem= 0; + + for (TABLE_LIST *tbl= tables; tbl != *tables_last; tbl= tbl->next_global) + { + if (tbl->derived) + continue; + if (!tbl->db.str && !tbl->with) + tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl); + if (!tbl->with) // no CTE matches table reference tbl + { + if (only_cte_resolution) + continue; + if (!tbl->db.str) // no database specified in table reference tbl + { + if (!thd->db.str) // no default database is set + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + return true; + } + if (copy_db_to(&tbl->db)) + return true; + if (!(tbl->table_options & TL_OPTION_ALIAS)) + MDL_REQUEST_INIT(&tbl->mdl_request, MDL_key::TABLE, + tbl->db.str, tbl->table_name.str, + tbl->mdl_type, MDL_TRANSACTION); + tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); + } + continue; + } + with_elem= tbl->with; + if (tbl->is_recursive_with_table() && + !tbl->is_with_table_recursive_reference()) + { + tbl->with->rec_outer_references++; + while ((with_elem= with_elem->get_next_mutually_recursive()) != + tbl->with) + with_elem->rec_outer_references++; + } + if (!with_elem->is_used_in_query || with_elem->is_recursive) + { + tbl->derived= with_elem->spec; + if (tbl->derived != tbl->select_lex->master_unit() && + !with_elem->is_recursive && + !tbl->is_with_table_recursive_reference()) + { + tbl->derived->move_as_slave(tbl->select_lex); + } + with_elem->is_used_in_query= true; + } + else + { + if (!(tbl->derived= tbl->with->clone_parsed_spec(thd->lex, tbl))) + return true; + } + tbl->db.str= empty_c_string; + tbl->db.length= 0; + tbl->schema_table= 0; + if (tbl->derived) + { + tbl->derived->first_select()->set_linkage(DERIVED_TABLE_TYPE); + tbl->select_lex->add_statistics(tbl->derived); + } + if (tbl->with->is_recursive && tbl->is_with_table_recursive_reference()) + continue; + with_elem->inc_references(); + } + return false; +} + + +/** + @brief + Find out dependencies between CTEs, resolve references to them + + @details + The function can be called in two modes. With this->with_cte_resolution + set to false the function only finds out all dependencies between CTEs + used in a query expression with a WITH clause whose parsing has been + just finished. Based on these dependencies recursive CTEs are detected. + If this->with_cte_resolution is set to true the function additionally + resolves all references to CTE occurred in this query expression. + + @retval + true on failure + false on success +*/ + +bool +LEX::check_cte_dependencies_and_resolve_references() +{ + if (check_dependencies_in_with_clauses()) + return true; + if (!with_cte_resolution) + return false; + if (resolve_references_to_cte(query_tables, query_tables_last)) + return true; + if (resolve_references_to_cte_in_hanging_cte()) + return true; + return false; +} + + /** @brief Check dependencies between tables defined in this with clause @@ -138,10 +333,11 @@ bool With_clause::check_dependencies() elem != with_elem; elem= elem->next) { - if (lex_string_cmp(system_charset_info, with_elem->query_name, - elem->query_name) == 0) + if (lex_string_cmp(system_charset_info, with_elem->get_name(), + elem->get_name()) == 0) { - my_error(ER_DUP_QUERY_NAME, MYF(0), with_elem->query_name->str); + my_error(ER_DUP_QUERY_NAME, MYF(0), + with_elem->get_name_str()); return true; } } @@ -248,13 +444,12 @@ With_element *With_clause::find_table_def(TABLE_LIST *table, with_elem != barrier; with_elem= with_elem->next) { - if (my_strcasecmp(system_charset_info, with_elem->query_name->str, - table->table_name.str) == 0 && + if (my_strcasecmp(system_charset_info, with_elem->get_name_str(), + table->table_name.str) == 0 && !table->is_fqtn) { table->set_derived(); - table->db.str= empty_c_string; - table->db.length= 0; + with_elem->referenced= true; return with_elem; } } @@ -611,7 +806,7 @@ bool With_clause::check_anchors() if (elem == with_elem) { my_error(ER_RECURSIVE_WITHOUT_ANCHORS, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -644,7 +839,7 @@ bool With_clause::check_anchors() if (elem->work_dep_map & elem->get_elem_map()) { my_error(ER_UNACCEPTABLE_MUTUAL_RECURSION, MYF(0), - with_elem->query_name->str); + with_elem->get_name_str()); return true; } } @@ -798,7 +993,8 @@ bool With_element::set_unparsed_spec(THD *thd, @brief Create a clone of the specification for the given with table - @param thd The context of the statement containing this with element + @param old_lex The LEX structure created for the query or CTE specification + where this With_element is defined @param with_table The reference to the table defined in this element for which the clone is created. @@ -808,12 +1004,13 @@ bool With_element::set_unparsed_spec(THD *thd, this element. The clone is created when the string with the specification saved in unparsed_spec is fed into the parser as an input string. The parsing - this string a unit object representing the specification is build. + this string a unit object representing the specification is built. A chain of all table references occurred in the specification is also formed. The method includes the new unit and its sub-unit into hierarchy of the units of the main query. I also insert the constructed chain of the table references into the chain of all table references of the main query. + The method resolves all references to CTE in the clone. @note Clones is created only for not first references to tables defined in @@ -829,116 +1026,128 @@ bool With_element::set_unparsed_spec(THD *thd, NULL - otherwise */ -st_select_lex_unit *With_element::clone_parsed_spec(THD *thd, +st_select_lex_unit *With_element::clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table) { + THD *thd= old_lex->thd; LEX *lex; - st_select_lex_unit *res= NULL; - Query_arena backup; - Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup); - bool has_tmp_tables; + st_select_lex_unit *res= NULL; if (!(lex= (LEX*) new(thd->mem_root) st_lex_local)) - { - if (arena) - thd->restore_active_arena(arena, &backup); return res; - } - LEX *old_lex= thd->lex; thd->lex= lex; bool parse_status= false; - Parser_state parser_state; - TABLE_LIST *spec_tables; - TABLE_LIST *spec_tables_tail; st_select_lex *with_select; char save_end= unparsed_spec.str[unparsed_spec.length]; ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= '\0'; - if (parser_state.init(thd, (char*) unparsed_spec.str, (unsigned int)unparsed_spec.length)) - goto err; - parser_state.m_lip.stmt_prepare_mode= stmt_prepare_mode; - parser_state.m_lip.multi_statements= false; - parser_state.m_lip.m_digest= NULL; lex_start(thd); lex->clone_spec_offset= unparsed_spec_offset; - lex->param_list= old_lex->param_list; - lex->sphead= old_lex->sphead; - lex->spname= old_lex->spname; - lex->spcont= old_lex->spcont; - lex->sp_chistics= old_lex->sp_chistics; - - lex->stmt_lex= old_lex; - parse_status= parse_sql(thd, &parser_state, 0); + lex->with_cte_resolution= true; + + /* + The specification of a CTE is to be parsed as a regular query. + At the very end of the parsing query the function + check_cte_dependencies_and_resolve_references() will be called. + It will check the dependencies between CTEs that are defined + within the query and will resolve CTE references in this query. + If a table reference is not resolved as a CTE reference within + this query it still can be resolved as a reference to a CTE defined + in the same clause as the CTE whose specification is to be parsed + or defined in an embedding CTE definition. + + Example: + with + cte1 as ( ... ), + cte2 as ([WITH ...] select ... from cte1 ...) + select ... from cte2 as r, ..., cte2 as s ... + + Here the specification of cte2 has be cloned for table reference + with alias s1. The specification contains a reference to cte1 + that is defined outside this specification. If the reference to + cte1 cannot be resolved within the specification of cte2 it's + not necessarily has to be a reference to a non-CTE table. That's + why the flag lex->only_cte_resolution has to be set to true + before parsing of the specification of cte2 invoked by this + function starts. Otherwise an mdl_lock would be requested for s + and this would not be correct. + */ + + lex->only_cte_resolution= true; + + lex->stmt_lex= old_lex->stmt_lex ? old_lex->stmt_lex : old_lex; + + parse_status= thd->sql_parser(old_lex, lex, + (char*) unparsed_spec.str, + (unsigned int)unparsed_spec.length, + stmt_prepare_mode); + ((char*) &unparsed_spec.str[unparsed_spec.length])[0]= save_end; - with_select= lex->first_select_lex(); + with_select= lex->unit.first_select(); if (parse_status) goto err; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - goto err; - - spec_tables= lex->query_tables; - spec_tables_tail= 0; - has_tmp_tables= thd->has_temporary_tables(); - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) - { - if (has_tmp_tables && !tbl->derived && !tbl->schema_table && - thd->open_temporary_table(tbl)) - goto err; - spec_tables_tail= tbl; - } - if (spec_tables) + /* + The global chain of TABLE_LIST objects created for the specification that + just has been parsed is added to such chain that contains the reference + to the CTE whose specification is parsed right after the TABLE_LIST object + created for the reference. + */ + if (lex->query_tables) { - if (with_table->next_global) + head->tables_pos.set_start_pos(&with_table->next_global); + head->tables_pos.set_end_pos(lex->query_tables_last); + TABLE_LIST *next_tbl= with_table->next_global; + if (next_tbl) { - spec_tables_tail->next_global= with_table->next_global; - with_table->next_global->prev_global= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= next_tbl->prev_global)= + lex->query_tables; + *(next_tbl->prev_global= lex->query_tables_last)= next_tbl; } else { - old_lex->query_tables_last= &spec_tables_tail->next_global; + *(lex->query_tables->prev_global= old_lex->query_tables_last)= + lex->query_tables; + old_lex->query_tables_last= lex->query_tables_last; } - spec_tables->prev_global= &with_table->next_global; - with_table->next_global= spec_tables; } res= &lex->unit; res->with_element= this; + /* + The unit of the specification that just has been parsed is included + as a slave of the select that contained in its from list the table + reference for which the unit has been created. + */ lex->unit.include_down(with_table->select_lex); - lex->unit.set_slave(with_select); + lex->unit.set_slave(with_select); + lex->unit.cloned_from= spec; old_lex->all_selects_list= (st_select_lex*) (lex->all_selects_list-> insert_chain_before( (st_select_lex_node **) &(old_lex->all_selects_list), with_select)); - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - res= NULL; + /* - Resolve references to CTE from the spec_tables list that has not - been resolved yet. + Now all references to the CTE defined outside of the cloned specification + has to be resolved. Additionally if old_lex->only_cte_resolution == false + for the table references that has not been resolved requests for mdl_locks + has to be set. */ - for (TABLE_LIST *tbl= spec_tables; - tbl; - tbl= tbl->next_global) + lex->only_cte_resolution= old_lex->only_cte_resolution; + if (lex->resolve_references_to_cte(lex->query_tables, + lex->query_tables_last)) { - if (!tbl->with) - tbl->with= with_select->find_table_def_in_with_clauses(tbl); - if (tbl == spec_tables_tail) - break; - } - if (check_table_access(thd, SELECT_ACL, spec_tables, FALSE, UINT_MAX, FALSE)) + res= NULL; goto err; + } - lex->sphead= NULL; // in order not to delete lex->sphead + lex->sphead= NULL; // in order not to delete lex->sphead lex_end(lex); err: - if (arena) - thd->restore_active_arena(arena, &backup); thd->lex= old_lex; return res; } @@ -1145,59 +1354,6 @@ With_element *st_select_lex::find_table_def_in_with_clauses(TABLE_LIST *table) } -/** - @brief - Set the specifying unit in this reference to a with table - - @details - The method assumes that the given element with_elem defines the table T - this table reference refers to. - If this is the first reference to T the method just sets its specification - in the field 'derived' as the unit that yields T. Otherwise the method - first creates a clone specification and sets rather this clone in this field. - - @retval - false on success - true on failure -*/ - -bool TABLE_LIST::set_as_with_table(THD *thd, With_element *with_elem) -{ - if (table) - { - /* - This table was prematurely identified as a temporary table. - We correct it here, but it's not a nice solution in the case - when the temporary table with this name is not used anywhere - else in the query. - */ - thd->mark_tmp_table_as_free_for_reuse(table); - table= 0; - } - with= with_elem; - schema_table= NULL; - if (!with_elem->is_referenced() || with_elem->is_recursive) - { - derived= with_elem->spec; - if (derived != select_lex->master_unit() && - !with_elem->is_recursive && - !is_with_table_recursive_reference()) - { - derived->move_as_slave(select_lex); - } - } - else - { - if(!(derived= with_elem->clone_parsed_spec(thd, this))) - return true; - } - derived->first_select()->set_linkage(DERIVED_TABLE_TYPE); - select_lex->add_statistics(derived); - with_elem->inc_references(); - return false; -} - - bool TABLE_LIST::is_recursive_with_table() { return with && with->is_recursive; @@ -1297,7 +1453,7 @@ bool st_select_lex::check_unrestricted_recursive(bool only_standard_compliant) if (only_standard_compliant && with_elem->is_unrestricted()) { my_error(ER_NOT_STANDARD_COMPLIANT_RECURSIVE, - MYF(0), with_elem->query_name->str); + MYF(0), with_elem->get_name_str()); return true; } @@ -1514,7 +1670,7 @@ static void list_strlex_print(THD *thd, String *str, List *list) void With_element::print(THD *thd, String *str, enum_query_type query_type) { - str->append(query_name); + str->append(get_name()); if (column_list.elements) { List_iterator_fast li(column_list); diff --git a/sql/sql_cte.h b/sql/sql_cte.h index 4c42dd23614..44628df3ff8 100644 --- a/sql/sql_cte.h +++ b/sql/sql_cte.h @@ -24,6 +24,39 @@ class select_unit; struct st_unit_ctxt_elem; +/** + @class With_element_head + @brief Head of the definition of a CTE table + + It contains the name of the CTE and it contains the position of the subchain + of table references used in the definition in the global chain of table + references used in the query where this definition is encountered. +*/ + +class With_element_head : public Sql_alloc +{ + /* The name of the defined CTE */ + LEX_CSTRING *query_name; + +public: + /* + The structure describing the subchain of the table references used in + the specification of the defined CTE in the global chain of table + references used in the query. The structure is fully defined only + after the CTE definition has been parsed. + */ + TABLE_CHAIN tables_pos; + + With_element_head(LEX_CSTRING *name) + : query_name(name) + { + tables_pos.set_start_pos(0); + tables_pos.set_end_pos(0); + } + friend class With_element; +}; + + /** @class With_element @brief Definition of a CTE table @@ -85,9 +118,22 @@ private: subqueries and specifications of other with elements). */ uint references; + + /* + true <=> this With_element is referred in the query in which the + element is defined + */ + bool referenced; + + /* + true <=> this With_element is needed for the execution of the query + in which the element is defined + */ + bool is_used_in_query; + /* Unparsed specification of the query that specifies this element. - It used to build clones of the specification if they are needed. + It's used to build clones of the specification if they are needed. */ LEX_CSTRING unparsed_spec; /* Offset of the specification in the input string */ @@ -101,10 +147,12 @@ private: public: /* - The name of the table introduced by this with elememt. The name - can be used in FROM lists of the queries in the scope of the element. + Contains the name of the defined With element and the position of + the subchain of the tables references used by its definition in the + global chain of TABLE_LIST objects created for the whole query. */ - LEX_CSTRING *query_name; + With_element_head *head; + /* Optional list of column names to name the columns of the table introduced by this with element. It is used in the case when the names are not @@ -163,18 +211,27 @@ public: /* List of derived tables containing recursive references to this CTE */ SQL_I_List derived_with_rec_ref; - With_element(LEX_CSTRING *name, + With_element(With_element_head *h, List list, st_select_lex_unit *unit) : next(NULL), base_dep_map(0), derived_dep_map(0), sq_dep_map(0), work_dep_map(0), mutually_recursive(0), top_level_dep_map(0), sq_rec_ref(NULL), next_mutually_recursive(NULL), references(0), - query_name(name), column_list(list), cycle_list(0), spec(unit), + referenced(false), is_used_in_query(false), + head(h), column_list(list), cycle_list(0), spec(unit), is_recursive(false), rec_outer_references(0), with_anchor(false), level(0), rec_result(NULL) { unit->with_element= this; } + LEX_CSTRING *get_name() { return head->query_name; } + const char *get_name_str() { return get_name()->str; } + + void set_tables_start_pos(TABLE_LIST **pos) + { head->tables_pos.set_start_pos(pos); } + void set_tables_end_pos(TABLE_LIST **pos) + { head->tables_pos.set_end_pos(pos); } + bool check_dependencies_in_spec(); void check_dependencies_in_select(st_select_lex *sl, st_unit_ctxt_elem *ctxt, @@ -201,9 +258,9 @@ public: bool set_unparsed_spec(THD *thd, const char *spec_start, const char *spec_end, my_ptrdiff_t spec_offset); - st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table); + st_select_lex_unit *clone_parsed_spec(LEX *old_lex, TABLE_LIST *with_table); - bool is_referenced() { return references != 0; } + bool is_referenced() { return referenced; } void inc_references() { references++; } @@ -263,6 +320,12 @@ public: void set_cycle_list(List *cycle_list_arg); friend class With_clause; + + friend + bool LEX::resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; const uint max_number_of_elements_in_with_clause= sizeof(table_map)*8; @@ -361,8 +424,10 @@ public: friend class With_element; friend - bool - check_dependencies_in_with_clauses(With_clause *with_clauses_list); + bool LEX::check_dependencies_in_with_clauses(); + + friend + bool LEX::resolve_references_to_cte_in_hanging_cte(); }; inline diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f16102d918b..de323ae336d 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1255,6 +1255,8 @@ void LEX::start(THD *thd_arg) explain_json= false; context_analysis_only= 0; derived_tables= 0; + with_cte_resolution= false; + only_cte_resolution= false; safe_to_cache_query= 1; parsing_options.reset(); empty_field_list_on_rset= 0; @@ -2911,6 +2913,7 @@ void st_select_lex_unit::init_query() is_view= false; with_clause= 0; with_element= 0; + cloned_from= 0; columns_are_renamed= false; with_wrapped_tvc= false; have_except_all_or_intersect_all= false; @@ -8997,6 +9000,8 @@ bool LEX::check_main_unit_semantics() if (unit.set_nest_level(0) || unit.check_parameters(first_select_lex())) return TRUE; + if (check_cte_dependencies_and_resolve_references()) + return TRUE; return FALSE; } @@ -9703,8 +9708,8 @@ bool LEX::main_select_push(bool service) { DBUG_ENTER("LEX::main_select_push"); DBUG_PRINT("info", ("service: %u", service)); - current_select_number= 1; - builtin_select.select_number= 1; + current_select_number= ++thd->lex->stmt_lex->current_select_number; + builtin_select.select_number= current_select_number; builtin_select.is_service_select= service; if (push_select(&builtin_select)) DBUG_RETURN(TRUE); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 45a3bf72fa4..4c15c755aa7 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -944,6 +944,8 @@ public: With_clause *with_clause; /* With element where this unit is used as the specification (if any) */ With_element *with_element; + /* The unit used as a CTE specification from which this unit is cloned */ + st_select_lex_unit *cloned_from; /* thread handler */ THD *thd; /* @@ -1543,7 +1545,9 @@ public: } With_element *get_with_element() { - return master_unit()->with_element; + return master_unit()->cloned_from ? + master_unit()->cloned_from->with_element : + master_unit()->with_element; } With_element *find_table_def_in_with_clauses(TABLE_LIST *table); bool check_unrestricted_recursive(bool only_standard_compliant); @@ -3389,6 +3393,20 @@ public: */ uint8 derived_tables; uint8 context_analysis_only; + /* + true <=> The parsed fragment requires resolution of references to CTE + at the end of parsing. This name resolution process involves searching + for possible dependencies between CTE defined in the parsed fragment and + detecting possible recursive references. + The flag is set to true if the fragment contains CTE definitions. + */ + bool with_cte_resolution; + /* + true <=> only resolution of references to CTE are required in the parsed + fragment, no checking of dependencies between CTE is required. + This flag is used only when parsing clones of CTE specifications. + */ + bool only_cte_resolution; bool local_file; bool check_exists; bool autocommit; @@ -4725,6 +4743,12 @@ public: const LEX_CSTRING *constraint_name, Table_ident *ref_table_name, DDL_options ddl_options); + bool check_dependencies_in_with_clauses(); + bool resolve_references_to_cte_in_hanging_cte(); + bool check_cte_dependencies_and_resolve_references(); + bool resolve_references_to_cte(TABLE_LIST *tables, + TABLE_LIST **tables_last); + }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0e2b80beb78..e9252f0b694 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3594,9 +3594,6 @@ mysql_execute_command(THD *thd) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); } - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - DBUG_RETURN(1); - #ifdef HAVE_REPLICATION if (unlikely(thd->slave_thread)) { @@ -8291,7 +8288,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->is_fqtn= TRUE; ptr->db= table->db; } - else if (lex->copy_db_to(&ptr->db)) + else if (!lex->with_cte_resolution && lex->copy_db_to(&ptr->db)) DBUG_RETURN(0); else ptr->is_fqtn= FALSE; @@ -8308,7 +8305,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, } ptr->table_name= table->table; - ptr->lock_type= lock_type; + ptr->lock_type= lock_type; + ptr->mdl_type= mdl_type; + ptr->table_options= table_options; ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING); /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */ ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX); @@ -8989,8 +8988,10 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update { tables->lock_type= lock_type; tables->updating= for_update; - tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + + if (tables->db.str && tables->db.str[0]) + tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } DBUG_VOID_RETURN; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 21958e24d91..5fac9071575 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2321,9 +2321,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) if (tables) thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto error; - if (sql_command_flags[sql_command] & CF_HA_CLOSE) mysql_ha_rm_tables(thd, tables); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index cfd43bd13ab..01fd42458a1 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -431,12 +431,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_type= OT_BASE_ONLY; - if (check_dependencies_in_with_clauses(lex->with_clauses_list)) - { - res= TRUE; - goto err_no_relink; - } - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* @@ -1419,9 +1413,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table, TABLE_LIST *tbl; Security_context *security_ctx= 0; - if (check_dependencies_in_with_clauses(thd->lex->with_clauses_list)) - goto err; - /* Check rights to run commands which show underlying tables. In the optimizer trace we would not like to show trace for diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 1a045c5416f..ec72e39bae3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -280,6 +280,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() class sp_head *sphead; class sp_name *spname; class sp_variable *spvar; + class With_element_head *with_element_head; class With_clause *with_clause; class Virtual_column_info *virtual_column; @@ -1753,7 +1754,7 @@ End SQL_MODE_ORACLE_SPECIFIC */ %type with_clause -%type query_name +%type with_element_head %type comma_separated_ident_list @@ -2946,7 +2947,11 @@ call: if (unlikely(Lex->call_statement_start(thd, $2))) MYSQL_YYABORT; } - opt_sp_cparam_list {} + opt_sp_cparam_list + { + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; + } ; /* CALL parameters */ @@ -3753,6 +3758,8 @@ expr_lex: $$->sp_lex_in_use= true; $$->set_item($2); Lex->pop_select(); //min select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; if ($$->sphead->restore_lex(thd)) MYSQL_YYABORT; } @@ -12649,6 +12656,8 @@ do: { Lex->insert_list= $3; Lex->pop_select(); //main select + if (Lex->check_cte_dependencies_and_resolve_references()) + MYSQL_YYABORT; } ; @@ -14831,6 +14840,7 @@ with_clause: if (unlikely(with_clause == NULL)) MYSQL_YYABORT; lex->derived_tables|= DERIVED_WITH; + lex->with_cte_resolution= true; lex->curr_with_clause= with_clause; with_clause->add_to_list(Lex->with_clauses_list_last_next); if (lex->current_select && @@ -14858,7 +14868,7 @@ with_list: with_list_element: - query_name + with_element_head opt_with_column_list AS '(' query_expression ')' opt_cycle { @@ -14876,6 +14886,7 @@ with_list_element: { elem->set_cycle_list($7); } + elem->set_tables_end_pos(lex->query_tables_last); } ; @@ -14936,12 +14947,15 @@ comma_separated_ident_list: ; -query_name: +with_element_head: ident { - $$= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); - if (unlikely($$ == NULL)) + LEX_CSTRING *name= + (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING)); + $$= new (thd->mem_root) With_element_head(name); + if (unlikely(name == NULL || $$ == NULL)) MYSQL_YYABORT; + $$->tables_pos.set_start_pos(Lex->query_tables_last); } ; diff --git a/sql/table.h b/sql/table.h index 58789dc3826..8efab4ca70d 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2109,6 +2109,29 @@ struct vers_select_conds_t struct LEX; class Index_hint; + +/* + @struct TABLE_CHAIN + @brief Subchain of global chain of table references + + The structure contains a pointer to the address of the next_global + pointer to the first TABLE_LIST objectof the subchain and the address + of the next_global pointer to the element right after the last + TABLE_LIST object of the subchain. For an empty subchain both pointers + have the same value. +*/ + +struct TABLE_CHAIN +{ + TABLE_CHAIN() {} + + TABLE_LIST **start_pos; + TABLE_LIST ** end_pos; + + void set_start_pos(TABLE_LIST **pos) { start_pos= pos; } + void set_end_pos(TABLE_LIST **pos) { end_pos= pos; } +}; + struct TABLE_LIST { TABLE_LIST() {} /* Remove gcc warning */ @@ -2443,6 +2466,20 @@ struct TABLE_LIST /* call back function for asking handler about caching in query cache */ qc_engine_callback callback_func; thr_lock_type lock_type; + + /* + Two fields below are set during parsing this table reference in the cases + when the table reference can be potentially a reference to a CTE table. + In this cases the fact that the reference is a reference to a CTE or not + will be ascertained at the very end of parsing of the query when referencies + to CTE are resolved. For references to CTE and to derived tables no mdl + requests are needed while for other table references they are. If a request + is possibly postponed the info that allows to issue this request must be + saved in 'mdl_type' and 'table_options'. + */ + enum_mdl_type mdl_type; + ulong table_options; + uint outer_join; /* Which join type */ uint shared; /* Used in multi-upd */ bool updatable; /* VIEW/TABLE can be updated now */ -- cgit v1.2.1