From 9d07b0520c65d8088a522e3d8b1292ad723dba15 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Fri, 6 Oct 2023 22:36:32 +0200 Subject: [PATCH] MDEV-31608 - Connector/NET fails to connect since 10.10 Connector/NET does not expect collation IDs returned by "show collations" to be NULL, runs into an exception. The fix is to determine connector/net using its connection attributes, then make sure "show collations" does not output NULL IDs. The patch introduces new old_mode NO_NULL_COLLATION_IDs, that is automatically set, once MySQL Connector/NET connection is determined. A test was added, that uses MySql.Data from powershell - only works if MySql.Data is installed into GAC (i.e with C/NET MSI package) --- mysql-test/main/mysql_connector_net.ps1 | 58 +++++++++++++++++++ mysql-test/main/mysql_connector_net.result | 2 + mysql-test/main/mysql_connector_net.test | 11 ++++ mysql-test/main/mysqld--help,win.rdiff | 20 ++++--- mysql-test/main/mysqld--help.result | 3 +- mysql-test/main/old-mode.result | 10 ++++ mysql-test/main/old-mode.test | 8 +++ .../suite/sys_vars/r/old_mode_basic.result | 4 +- .../sys_vars/r/sysvars_server_embedded.result | 2 +- .../r/sysvars_server_notembedded.result | 2 +- .../suite/sys_vars/t/old_mode_basic.test | 2 +- sql/sql_acl.cc | 41 +++++++++++-- sql/sql_class.h | 1 + sql/sql_show.cc | 3 +- sql/sys_vars.cc | 1 + 15 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 mysql-test/main/mysql_connector_net.ps1 create mode 100644 mysql-test/main/mysql_connector_net.result create mode 100644 mysql-test/main/mysql_connector_net.test diff --git a/mysql-test/main/mysql_connector_net.ps1 b/mysql-test/main/mysql_connector_net.ps1 new file mode 100644 index 00000000000..159acf9361c --- /dev/null +++ b/mysql-test/main/mysql_connector_net.ps1 @@ -0,0 +1,58 @@ +$assembly = [system.reflection.Assembly]::LoadWithPartialName("MySql.Data") +if ($assembly -eq $null) +{ + "Can't load assembly MySql.Data" + exit 100 +} + +try +{ + $connectionString =[string]::Format("server=127.0.0.1;uid=root;port={0};Connection Reset=true;",$Env:MASTER_MYPORT) + $connection = [MySql.Data.MySqlClient.MySqlConnection]@{ConnectionString=$connectionString} + $connection.Open() + + # Test ExecuteReader() + $command = New-Object MySql.Data.MySqlClient.MySqlCommand + $command.Connection = $connection + $command.CommandText = "SELECT @@old_mode" + $reader = $command.ExecuteReader() + $reader.GetName(0) + while ($reader.Read()) + { + $reader.GetValue(0) + } + + # Test connection reset + $connection.Close() + $connection.Open() + # Test ExecuteNonQuery() + $command.CommandText="do 1"; + $affected_rows = $command.ExecuteNonQuery() + if ($affected_rows -ne 0) + { + "Expected affected rows 0, actual $affected_rows" + exit 1 + } + # Test Prepared Statement + $command.CommandText = "SELECT @var"; + [void]$command.Parameters.AddWithValue("@var", 1); + $command.Prepare(); + $out = $command.ExecuteScalar(); + if ($out -ne 1) + { + "Expected output 1, actual $out" + exit 1 + } + $connection.Close() +} +catch +{ + # Dump exception + $_ + $inner = $PSItem.Exception.InnerException + if ($inner -ne $null) + { + $PSItem.Exception.InnerException.Message + $PSItem.Exception.InnerException.StackTrace + } +} diff --git a/mysql-test/main/mysql_connector_net.result b/mysql-test/main/mysql_connector_net.result new file mode 100644 index 00000000000..f2fa39df3e7 --- /dev/null +++ b/mysql-test/main/mysql_connector_net.result @@ -0,0 +1,2 @@ +@@old_mode +UTF8_IS_UTF8MB3,NO_NULL_COLLATION_IDS diff --git a/mysql-test/main/mysql_connector_net.test b/mysql-test/main/mysql_connector_net.test new file mode 100644 index 00000000000..c1dce65adc8 --- /dev/null +++ b/mysql-test/main/mysql_connector_net.test @@ -0,0 +1,11 @@ +--source include/windows.inc +let $sys_errno=0; + +# Error 100 is returned by the powershell script +# if MySql.Data is not installed +--error 0,100 +--exec powershell -NoLogo -NoProfile -File main\mysql_connector_net.ps1 +if ($sys_errno != 0) +{ + --skip Connector/NET is not installed +} diff --git a/mysql-test/main/mysqld--help,win.rdiff b/mysql-test/main/mysqld--help,win.rdiff index 3e9541d7d2f..a42c0c6f81f 100644 --- a/mysql-test/main/mysqld--help,win.rdiff +++ b/mysql-test/main/mysqld--help,win.rdiff @@ -1,4 +1,6 @@ -@@ -180,6 +180,7 @@ +--- main/mysqld--help.result 2023-11-30 02:21:51.951132200 +0100 ++++ main/mysqld--help,win.reject 2023-11-30 02:35:44.404612300 +0100 +@@ -191,6 +191,7 @@ --console Write error output on screen; don't remove the console window on windows. --core-file Write core on crashes @@ -6,7 +8,7 @@ -h, --datadir=name Path to the database root directory --date-format=name The DATE format (ignored) --datetime-format=name -@@ -650,6 +651,7 @@ +@@ -696,6 +697,7 @@ Use MySQL-5.6 (instead of MariaDB-5.3) format for TIME, DATETIME, TIMESTAMP columns. (Defaults to on; use --skip-mysql56-temporal-format to disable.) @@ -14,7 +16,7 @@ --net-buffer-length=# Buffer length for TCP/IP and socket communication --net-read-timeout=# -@@ -1327,6 +1328,10 @@ +@@ -1351,6 +1353,10 @@ Alias for log_slow_query_file. Log slow queries to given log file. Defaults logging to 'hostname'-slow.log. Must be enabled to activate other slow log options @@ -25,7 +27,7 @@ --socket=name Socket file to use for connection --sort-buffer-size=# Each thread that needs to do a sort allocates a buffer of -@@ -1351,6 +1356,7 @@ +@@ -1376,6 +1382,7 @@ deleting or updating every row in a table. --stack-trace Print a symbolic stack trace on failure (Defaults to on; use --skip-stack-trace to disable.) @@ -33,7 +35,7 @@ --standard-compliant-cte Allow only CTEs compliant to SQL standard (Defaults to on; use --skip-standard-compliant-cte to disable.) -@@ -1426,6 +1432,11 @@ +@@ -1454,6 +1461,11 @@ --thread-pool-max-threads=# Maximum allowed number of worker threads in the thread pool @@ -45,7 +47,7 @@ --thread-pool-oversubscribe=# How many additional active worker threads in a group are allowed. -@@ -1464,8 +1475,8 @@ +@@ -1493,8 +1505,8 @@ automatically convert it to an on-disk MyISAM or Aria table. -t, --tmpdir=name Path for temporary files. Several paths may be specified, @@ -56,7 +58,7 @@ --transaction-alloc-block-size=# Allocation block size for transactions to be stored in binary log -@@ -1685,6 +1696,7 @@ +@@ -1716,6 +1728,7 @@ myisam-stats-method NULLS_UNEQUAL myisam-use-mmap FALSE mysql56-temporal-format TRUE @@ -64,7 +66,7 @@ net-buffer-length 16384 net-read-timeout 30 net-retry-count 10 -@@ -1841,6 +1853,7 @@ +@@ -1874,6 +1887,7 @@ slave-type-conversions slow-launch-time 2 slow-query-log FALSE @@ -72,7 +74,7 @@ sort-buffer-size 2097152 sql-mode STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION sql-safe-updates FALSE -@@ -1867,6 +1880,8 @@ +@@ -1901,6 +1915,8 @@ thread-pool-exact-stats FALSE thread-pool-idle-timeout 60 thread-pool-max-threads 65536 diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index d271f1a308d..867d5cd3c57 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -721,7 +721,8 @@ The following specify which files/extra groups are read (specified before remain MySQL versions. Any combination of: NO_DUP_KEY_WARNINGS_WITH_IGNORE, NO_PROGRESS_INFO, ZERO_DATE_TIME_CAST, UTF8_IS_UTF8MB3, - IGNORE_INDEX_ONLY_FOR_JOIN, COMPAT_5_1_CHECKSUM + IGNORE_INDEX_ONLY_FOR_JOIN, COMPAT_5_1_CHECKSUM, + NO_NULL_COLLATION_IDS Use 'ALL' to set all combinations. --old-passwords Use old password encryption method (needed for 4.0 and older clients) diff --git a/mysql-test/main/old-mode.result b/mysql-test/main/old-mode.result index daa2a4dc915..cb87c45ada8 100644 --- a/mysql-test/main/old-mode.result +++ b/mysql-test/main/old-mode.result @@ -257,3 +257,13 @@ Warning 1264 Out of range value for column 'a' at row 2 DROP TABLE t1; SET @@time_zone=DEFAULT; SET TIMESTAMP=DEFAULT; +# +# MDEV-31608 - Connector/NET fails to connect since 10.10 +# +select count(*) > 0 from information_schema.collations where id IS NULL; +count(*) > 0 +1 +SET old_mode=no_null_collation_ids; +select count(*) > 0 from information_schema.collations where id IS NULL; +count(*) > 0 +0 diff --git a/mysql-test/main/old-mode.test b/mysql-test/main/old-mode.test index e4928329b47..177e00edce8 100644 --- a/mysql-test/main/old-mode.test +++ b/mysql-test/main/old-mode.test @@ -169,3 +169,11 @@ DROP TABLE t1; SET @@time_zone=DEFAULT; SET TIMESTAMP=DEFAULT; + +--echo # +--echo # MDEV-31608 - Connector/NET fails to connect since 10.10 +--echo # +select count(*) > 0 from information_schema.collations where id IS NULL; +SET old_mode=no_null_collation_ids; +select count(*) > 0 from information_schema.collations where id IS NULL; + diff --git a/mysql-test/suite/sys_vars/r/old_mode_basic.result b/mysql-test/suite/sys_vars/r/old_mode_basic.result index 252316dc1cb..776d45a1fe3 100644 --- a/mysql-test/suite/sys_vars/r/old_mode_basic.result +++ b/mysql-test/suite/sys_vars/r/old_mode_basic.result @@ -114,8 +114,8 @@ SET @@global.old_mode = 4; SELECT @@global.old_mode; @@global.old_mode ZERO_DATE_TIME_CAST -SET @@global.old_mode = 64; -ERROR 42000: Variable 'old_mode' can't be set to the value of '64' +SET @@global.old_mode = 128; +ERROR 42000: Variable 'old_mode' can't be set to the value of '128' SELECT @@global.old_mode; @@global.old_mode ZERO_DATE_TIME_CAST diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 48eb32e9698..ac8752ac82e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -2299,7 +2299,7 @@ VARIABLE_COMMENT Used to emulate old behavior from earlier MariaDB or MySQL vers NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM +ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OLD_PASSWORDS diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 6cb556308a8..1f600a8a718 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -2469,7 +2469,7 @@ VARIABLE_COMMENT Used to emulate old behavior from earlier MariaDB or MySQL vers NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM +ENUM_VALUE_LIST NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,NO_NULL_COLLATION_IDS READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME OLD_PASSWORDS diff --git a/mysql-test/suite/sys_vars/t/old_mode_basic.test b/mysql-test/suite/sys_vars/t/old_mode_basic.test index 631d638767f..cb18796729e 100644 --- a/mysql-test/suite/sys_vars/t/old_mode_basic.test +++ b/mysql-test/suite/sys_vars/t/old_mode_basic.test @@ -172,7 +172,7 @@ SET @@global.old_mode = 4; SELECT @@global.old_mode; --Error ER_WRONG_VALUE_FOR_VAR -SET @@global.old_mode = 64; +SET @@global.old_mode = 128; SELECT @@global.old_mode; # use of decimal values diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 6b6e1c30da1..ebd2949a64e 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -13576,8 +13576,37 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) DBUG_RETURN(0); } + +/** + Determine if the client is MySQL Connector/NET. + + Checks whether the given connection attributes blob corresponds to + MySQL Connector/NET by examining the "_client_name" attribute, which is + expected to be the first attribute in the blob. + + @param connection_attrs - The connection attributes blob. + @param length - The length of the blob. + + @return true if the client is MySQL Connector/NET, false otherwise. +*/ +static inline bool is_connector_net_client(const char *connection_attrs, + size_t length) +{ + constexpr LEX_CSTRING prefix= + {STRING_WITH_LEN("\x0c_client_name\x13mysql-connector-net")}; + + if (length < prefix.length) + return false; + + /* Optimization to avoid following memcmp in common cases.*/ + if (connection_attrs[prefix.length - 1] != prefix.str[prefix.length - 1]) + return false; + + return !memcmp(connection_attrs, prefix.str, prefix.length); +} + static bool -read_client_connect_attrs(char **ptr, char *end, CHARSET_INFO *from_cs) +read_client_connect_attrs(char **ptr, char *end, THD* thd) { ulonglong length; char *ptr_save= *ptr; @@ -13600,10 +13629,14 @@ read_client_connect_attrs(char **ptr, char *end, CHARSET_INFO *from_cs) if (length > 65535) return true; - if (PSI_CALL_set_thread_connect_attrs(*ptr, (uint)length, from_cs) && + if (PSI_CALL_set_thread_connect_attrs(*ptr, (uint)length, thd->charset()) && current_thd->variables.log_warnings) sql_print_warning("Connection attributes of length %llu were truncated", length); + + /* Connector/Net crashes, when "show collations" returns NULL IDs*/ + if (is_connector_net_client(*ptr, length)) + thd->variables.old_behavior |= OLD_MODE_NO_NULL_COLLATION_IDS; return false; } @@ -13737,7 +13770,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) } if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && - read_client_connect_attrs(&next_field, end, thd->charset())) + read_client_connect_attrs(&next_field, end, thd)) { my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), MYF(0)); @@ -13987,7 +14020,7 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && read_client_connect_attrs(&next_field, ((char *)net->read_pos) + pkt_len, - mpvio->auth_info.thd->charset())) + mpvio->auth_info.thd)) return packet_error; /* diff --git a/sql/sql_class.h b/sql/sql_class.h index beb33d8394e..b53de8d55c0 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -201,6 +201,7 @@ enum enum_binlog_row_image { #define OLD_MODE_UTF8_IS_UTF8MB3 (1 << 3) #define OLD_MODE_IGNORE_INDEX_ONLY_FOR_JOIN (1 << 4) #define OLD_MODE_COMPAT_5_1_CHECKSUM (1 << 5) +#define OLD_MODE_NO_NULL_COLLATION_IDS (1 << 6) extern char internal_table_name[2]; extern char empty_c_string[1]; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 32b29468c32..c21043aaba2 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -6398,7 +6398,8 @@ int fill_schema_collation(THD *thd, TABLE_LIST *tables, COND *cond) tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_CONTEXT); LEX_CSTRING full_collation_name= tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_FULL); - bool is_context= cmp(context_collation_name, full_collation_name); + bool is_context= cmp(context_collation_name, full_collation_name) && + !(thd->variables.old_behavior & OLD_MODE_NO_NULL_COLLATION_IDS); /* Some collations are applicable to multiple character sets. Display them only once, with the short name (without the diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 0319298a9d7..23f1ca3a259 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3886,6 +3886,7 @@ static const char *old_mode_names[]= "UTF8_IS_UTF8MB3", "IGNORE_INDEX_ONLY_FOR_JOIN", "COMPAT_5_1_CHECKSUM", + "NO_NULL_COLLATION_IDS", 0 };