From 3f50c14f734b23025d98f0d090416cec2a1940cf Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 16 Jul 2024 20:49:24 -0700 Subject: [PATCH] Ensure all server variables specify the right character set. Signed-off-by: Bradley Grainger --- src/MySqlConnector/Core/ServerSession.cs | 5 ++- .../Protocol/Payloads/OkPayload.cs | 39 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index e05b2d6cf..dcaf1e69f 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -538,7 +538,8 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella if (m_useCompression) m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler); - if (ok.ClientCharacterSet != (ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? "utf8mb4" : "utf8")) + // send 'SET NAMES' to set the character set and collation unless the server reports that it's already using the desired character set (e.g., MariaDB >= 11.5) + if (ok.NewCharacterSet != (ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? CharacterSet.Utf8Mb4Binary : CharacterSet.Utf8Mb3Binary)) { // set 'collation_connection' to the server default await SendAsync(m_setNamesPayload, ioBehavior, cancellationToken).ConfigureAwait(false); @@ -550,7 +551,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella { await GetRealServerDetailsAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); } - else if (ok.ConnectionId is int newConnectionId && newConnectionId != ConnectionId) + else if (ok.NewConnectionId is int newConnectionId && newConnectionId != ConnectionId) { Log.ChangingConnectionId(m_logger, Id, ConnectionId, newConnectionId, ServerVersion.OriginalString, ServerVersion.OriginalString); ConnectionId = newConnectionId; diff --git a/src/MySqlConnector/Protocol/Payloads/OkPayload.cs b/src/MySqlConnector/Protocol/Payloads/OkPayload.cs index 95e77959c..50be1db79 100644 --- a/src/MySqlConnector/Protocol/Payloads/OkPayload.cs +++ b/src/MySqlConnector/Protocol/Payloads/OkPayload.cs @@ -14,8 +14,8 @@ internal sealed class OkPayload public int WarningCount { get; } public string? StatusInfo { get; } public string? NewSchema { get; } - public string? ClientCharacterSet { get; } - public int? ConnectionId { get; } + public CharacterSet? NewCharacterSet { get; } + public int? NewConnectionId { get; } public const byte Signature = 0x00; @@ -61,7 +61,9 @@ public static void Verify(ReadOnlySpan span, IServerCapabilities serverCap var serverStatus = (ServerStatus) reader.ReadUInt16(); var warningCount = (int) reader.ReadUInt16(); string? newSchema = null; - string? clientCharacterSet = null; + CharacterSet clientCharacterSet = default; + CharacterSet connectionCharacterSet = default; + CharacterSet resultsCharacterSet = default; int? connectionId = null; ReadOnlySpan statusBytes; @@ -93,7 +95,21 @@ public static void Verify(ReadOnlySpan span, IServerCapabilities serverCap var systemVariableValue = systemVariableValueLength == -1 ? default : reader.ReadByteString(systemVariableValueLength); if (systemVariableName.SequenceEqual("character_set_client"u8) && systemVariableValueLength != 0) { - clientCharacterSet = Encoding.ASCII.GetString(systemVariableValue); + clientCharacterSet = systemVariableValue.SequenceEqual("utf8mb4"u8) ? CharacterSet.Utf8Mb4Binary : + systemVariableValue.SequenceEqual("utf8"u8) ? CharacterSet.Utf8Mb3Binary : + CharacterSet.None; + } + else if (systemVariableName.SequenceEqual("character_set_connection"u8) && systemVariableValueLength != 0) + { + connectionCharacterSet = systemVariableValue.SequenceEqual("utf8mb4"u8) ? CharacterSet.Utf8Mb4Binary : + systemVariableValue.SequenceEqual("utf8"u8) ? CharacterSet.Utf8Mb3Binary : + CharacterSet.None; + } + else if (systemVariableName.SequenceEqual("character_set_results"u8) && systemVariableValueLength != 0) + { + resultsCharacterSet = systemVariableValue.SequenceEqual("utf8mb4"u8) ? CharacterSet.Utf8Mb4Binary : + systemVariableValue.SequenceEqual("utf8"u8) ? CharacterSet.Utf8Mb3Binary : + CharacterSet.None; } else if (systemVariableName.SequenceEqual("connection_id"u8)) { @@ -129,7 +145,12 @@ public static void Verify(ReadOnlySpan span, IServerCapabilities serverCap { var statusInfo = statusBytes.Length == 0 ? null : Encoding.UTF8.GetString(statusBytes); - if (affectedRowCount == 0 && lastInsertId == 0 && warningCount == 0 && statusInfo is null && newSchema is null && clientCharacterSet is null && connectionId is null) + // detect the connection character set as utf8mb4 (or utf8) if all three system variables are set to the same value + var characterSet = clientCharacterSet == CharacterSet.Utf8Mb4Binary && connectionCharacterSet == CharacterSet.Utf8Mb4Binary && resultsCharacterSet == CharacterSet.Utf8Mb4Binary ? CharacterSet.Utf8Mb4Binary : + clientCharacterSet == CharacterSet.Utf8Mb3Binary && connectionCharacterSet == CharacterSet.Utf8Mb3Binary && resultsCharacterSet == CharacterSet.Utf8Mb3Binary ? CharacterSet.Utf8Mb3Binary : + CharacterSet.None; + + if (affectedRowCount == 0 && lastInsertId == 0 && warningCount == 0 && statusInfo is null && newSchema is null && clientCharacterSet is CharacterSet.None && connectionId is null) { if (serverStatus == ServerStatus.AutoCommit) return s_autoCommitOk; @@ -137,7 +158,7 @@ public static void Verify(ReadOnlySpan span, IServerCapabilities serverCap return s_autoCommitSessionStateChangedOk; } - return new OkPayload(affectedRowCount, lastInsertId, serverStatus, warningCount, statusInfo, newSchema, clientCharacterSet, connectionId); + return new OkPayload(affectedRowCount, lastInsertId, serverStatus, warningCount, statusInfo, newSchema, characterSet, connectionId); } else { @@ -145,7 +166,7 @@ public static void Verify(ReadOnlySpan span, IServerCapabilities serverCap } } - private OkPayload(ulong affectedRowCount, ulong lastInsertId, ServerStatus serverStatus, int warningCount, string? statusInfo, string? newSchema, string? clientCharacterSet, int? connectionId) + private OkPayload(ulong affectedRowCount, ulong lastInsertId, ServerStatus serverStatus, int warningCount, string? statusInfo, string? newSchema, CharacterSet newCharacterSet, int? connectionId) { AffectedRowCount = affectedRowCount; LastInsertId = lastInsertId; @@ -153,8 +174,8 @@ private OkPayload(ulong affectedRowCount, ulong lastInsertId, ServerStatus serve WarningCount = warningCount; StatusInfo = statusInfo; NewSchema = newSchema; - ClientCharacterSet = clientCharacterSet; - ConnectionId = connectionId; + NewCharacterSet = newCharacterSet; + NewConnectionId = connectionId; } private static readonly OkPayload s_autoCommitOk = new(0, 0, ServerStatus.AutoCommit, 0, default, default, default, default);