From a6b86d78def35686858ed57ef976e5baa3954798 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:13:58 +0000 Subject: [PATCH 1/7] Initial plan From 3e141d378c3f0acb072abbf5b250c2c21c9e0597 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:26:07 +0000 Subject: [PATCH 2/7] Add server ID verification for KILL QUERY commands Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- src/MySqlConnector/Core/ServerSession.cs | 121 +++++++++++++++ src/MySqlConnector/Core/ServerVersions.cs | 3 + src/MySqlConnector/Logging/EventIds.cs | 21 ++- src/MySqlConnector/Logging/Log.cs | 15 ++ .../ServerIdentificationTests.cs | 138 ++++++++++++++++++ 5 files changed, 290 insertions(+), 8 deletions(-) create mode 100644 tests/MySqlConnector.Tests/ServerIdentificationTests.cs diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index 23efbfb44..68dd7a7d3 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -47,6 +47,8 @@ public ServerSession(ILogger logger, IConnectionPoolMetadata pool) public int ActiveCommandId { get; private set; } public int CancellationTimeout { get; private set; } public int ConnectionId { get; set; } + public string? ServerUuid { get; set; } + public long? ServerId { get; set; } public byte[]? AuthPluginData { get; set; } public long CreatedTimestamp { get; } public ConnectionPool? Pool { get; } @@ -117,6 +119,14 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma return; } + // Verify server identity before executing KILL QUERY to prevent cancelling on the wrong server + var killSession = killCommand.Connection!.Session; + if (!VerifyServerIdentity(killSession)) + { + Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerUuid, killSession.ServerUuid, ServerId, killSession.ServerId); + return; + } + // NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation. // For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId // to zero, then start executing a new command. By the time this "KILL QUERY" command reached the server, the wrong @@ -137,6 +147,26 @@ public void AbortCancel(ICancellableCommand command) } } + private bool VerifyServerIdentity(ServerSession otherSession) + { + // If server UUID is available, use it as the primary identifier (most unique) + if (!string.IsNullOrEmpty(ServerUuid) && !string.IsNullOrEmpty(otherSession.ServerUuid)) + { + return string.Equals(ServerUuid, otherSession.ServerUuid, StringComparison.Ordinal); + } + + // Fall back to server ID if UUID is not available + if (ServerId.HasValue && otherSession.ServerId.HasValue) + { + return ServerId.Value == otherSession.ServerId.Value; + } + + // If no server identification is available, allow the operation to proceed + // This maintains backward compatibility with older MySQL versions + Log.NoServerIdentificationForVerification(m_logger, Id, otherSession.Id); + return true; + } + public bool IsCancelingQuery => m_state == State.CancelingQuery; public async Task PrepareAsync(IMySqlCommand command, IOBehavior ioBehavior, CancellationToken cancellationToken) @@ -635,6 +665,9 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella ConnectionId = newConnectionId; } + // Get server identification for KILL QUERY verification + await GetServerIdentificationAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + m_payloadHandler.ByteHandler.RemainingTimeout = Constants.InfiniteTimeout; return redirectionUrl; } @@ -1951,6 +1984,90 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation } } + private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) + { + Log.GettingServerIdentification(m_logger, Id); + try + { + PayloadData payload; + + // Try to get both server_uuid and server_id if server supports server_uuid (MySQL 5.6+) + if (!ServerVersion.IsMariaDb && ServerVersion.Version >= ServerVersions.SupportsServerUuid) + { + payload = SupportsQueryAttributes ? s_selectServerIdWithAttributesPayload : s_selectServerIdNoAttributesPayload; + await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); + + // column count: 2 + _ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); + + // @@server_uuid and @@server_id columns + _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + + if (!SupportsDeprecateEof) + { + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + _ = EofPayload.Create(payload.Span); + } + + // first (and only) row + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + + var reader = new ByteArrayReader(payload.Span); + var length = reader.ReadLengthEncodedIntegerOrNull(); + var serverUuid = length != -1 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null; + length = reader.ReadLengthEncodedIntegerOrNull(); + var serverId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); + + ServerUuid = serverUuid; + ServerId = serverId; + + Log.RetrievedServerIdentification(m_logger, Id, serverUuid, serverId); + } + else + { + // Fall back to just server_id for older versions or MariaDB + payload = SupportsQueryAttributes ? s_selectServerIdOnlyWithAttributesPayload : s_selectServerIdOnlyNoAttributesPayload; + await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); + + // column count: 1 + _ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); + + // @@server_id column + _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + + if (!SupportsDeprecateEof) + { + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + _ = EofPayload.Create(payload.Span); + } + + // first (and only) row + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + + var reader = new ByteArrayReader(payload.Span); + var length = reader.ReadLengthEncodedIntegerOrNull(); + var serverId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); + + ServerUuid = null; + ServerId = serverId; + + Log.RetrievedServerIdentification(m_logger, Id, null, serverId); + } + + // OK/EOF payload + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + if (OkPayload.IsOk(payload.Span, this)) + OkPayload.Verify(payload.Span, this); + else + EofPayload.Create(payload.Span); + } + catch (MySqlException ex) + { + Log.FailedToGetServerIdentification(m_logger, ex, Id); + } + } + private void ShutdownSocket() { Log.ClosingStreamSocket(m_logger, Id); @@ -2182,6 +2299,10 @@ protected override void OnStatementBegin(int index) private static readonly PayloadData s_sleepWithAttributesPayload = QueryPayload.Create(true, "SELECT SLEEP(0) INTO @__MySqlConnector__Sleep;"u8); private static readonly PayloadData s_selectConnectionIdVersionNoAttributesPayload = QueryPayload.Create(false, "SELECT CONNECTION_ID(), VERSION();"u8); private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload.Create(true, "SELECT CONNECTION_ID(), VERSION();"u8); + private static readonly PayloadData s_selectServerIdNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_uuid, @@server_id;"u8); + private static readonly PayloadData s_selectServerIdWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_uuid, @@server_id;"u8); + private static readonly PayloadData s_selectServerIdOnlyNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_id;"u8); + private static readonly PayloadData s_selectServerIdOnlyWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_id;"u8); private readonly ILogger m_logger; #if NET9_0_OR_GREATER diff --git a/src/MySqlConnector/Core/ServerVersions.cs b/src/MySqlConnector/Core/ServerVersions.cs index fc8e498f5..1e02fad8c 100644 --- a/src/MySqlConnector/Core/ServerVersions.cs +++ b/src/MySqlConnector/Core/ServerVersions.cs @@ -19,4 +19,7 @@ internal static class ServerVersions // https://mariadb.com/kb/en/set-statement/ public static readonly Version MariaDbSupportsPerQueryVariables = new(10, 1, 2); + + // https://dev.mysql.com/doc/refman/5.6/en/replication-options.html#sysvar_server_uuid + public static readonly Version SupportsServerUuid = new(5, 6, 0); } diff --git a/src/MySqlConnector/Logging/EventIds.cs b/src/MySqlConnector/Logging/EventIds.cs index de87e8590..234e5c240 100644 --- a/src/MySqlConnector/Logging/EventIds.cs +++ b/src/MySqlConnector/Logging/EventIds.cs @@ -78,14 +78,17 @@ internal static class EventIds public const int DetectedProxy = 2150; public const int ChangingConnectionId = 2151; public const int FailedToGetConnectionId = 2152; - public const int CreatingConnectionAttributes = 2153; - public const int ObtainingPasswordViaProvidePasswordCallback = 2154; - public const int FailedToObtainPassword = 2155; - public const int ConnectedTlsBasicPreliminary = 2156; - public const int ConnectedTlsDetailedPreliminary = 2157; - public const int CertificateErrorUnixSocket = 2158; - public const int CertificateErrorNoPassword = 2159; - public const int CertificateErrorValidThumbprint = 2160; + public const int GettingServerIdentification = 2153; + public const int RetrievedServerIdentification = 2154; + public const int FailedToGetServerIdentification = 2155; + public const int CreatingConnectionAttributes = 2156; + public const int ObtainingPasswordViaProvidePasswordCallback = 2157; + public const int FailedToObtainPassword = 2158; + public const int ConnectedTlsBasicPreliminary = 2159; + public const int ConnectedTlsDetailedPreliminary = 2160; + public const int CertificateErrorUnixSocket = 2161; + public const int CertificateErrorNoPassword = 2162; + public const int CertificateErrorValidThumbprint = 2163; // Command execution events, 2200-2299 public const int CannotExecuteNewCommandInState = 2200; @@ -108,6 +111,8 @@ internal static class EventIds public const int IgnoringCancellationForInactiveCommand = 2306; public const int CancelingCommand = 2307; public const int SendingSleepToClearPendingCancellation = 2308; + public const int IgnoringCancellationForDifferentServer = 2309; + public const int NoServerIdentificationForVerification = 2310; // Cached procedure events, 2400-2499 public const int GettingCachedProcedure = 2400; diff --git a/src/MySqlConnector/Logging/Log.cs b/src/MySqlConnector/Logging/Log.cs index e9b4f88bc..56c171eaa 100644 --- a/src/MySqlConnector/Logging/Log.cs +++ b/src/MySqlConnector/Logging/Log.cs @@ -189,6 +189,21 @@ internal static partial class Log [LoggerMessage(EventIds.FailedToGetConnectionId, LogLevel.Information, "Session {SessionId} failed to get CONNECTION_ID(), VERSION()")] public static partial void FailedToGetConnectionId(ILogger logger, Exception exception, string sessionId); + [LoggerMessage(EventIds.GettingServerIdentification, LogLevel.Debug, "Session {SessionId} getting server identification")] + public static partial void GettingServerIdentification(ILogger logger, string sessionId); + + [LoggerMessage(EventIds.RetrievedServerIdentification, LogLevel.Debug, "Session {SessionId} retrieved server identification: UUID={ServerUuid}, ID={ServerId}")] + public static partial void RetrievedServerIdentification(ILogger logger, string sessionId, string? serverUuid, long? serverId); + + [LoggerMessage(EventIds.FailedToGetServerIdentification, LogLevel.Information, "Session {SessionId} failed to get server identification")] + public static partial void FailedToGetServerIdentification(ILogger logger, Exception exception, string sessionId); + + [LoggerMessage(EventIds.IgnoringCancellationForDifferentServer, LogLevel.Warning, "Session {SessionId} ignoring cancellation from session {KillSessionId}: server identity mismatch (this UUID={ServerUuid}, kill UUID={KillServerUuid}, this ID={ServerId}, kill ID={KillServerId})")] + public static partial void IgnoringCancellationForDifferentServer(ILogger logger, string sessionId, string killSessionId, string? serverUuid, string? killServerUuid, long? serverId, long? killServerId); + + [LoggerMessage(EventIds.NoServerIdentificationForVerification, LogLevel.Debug, "Session {SessionId} and kill session {KillSessionId} have no server identification available for verification")] + public static partial void NoServerIdentificationForVerification(ILogger logger, string sessionId, string killSessionId); + [LoggerMessage(EventIds.ClosingStreamSocket, LogLevel.Debug, "Session {SessionId} closing stream/socket")] public static partial void ClosingStreamSocket(ILogger logger, string sessionId); diff --git a/tests/MySqlConnector.Tests/ServerIdentificationTests.cs b/tests/MySqlConnector.Tests/ServerIdentificationTests.cs new file mode 100644 index 000000000..77850d12c --- /dev/null +++ b/tests/MySqlConnector.Tests/ServerIdentificationTests.cs @@ -0,0 +1,138 @@ +using MySqlConnector.Core; +using MySqlConnector.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace MySqlConnector.Tests; + +public class ServerIdentificationTests +{ + [Fact] + public void VerifyServerIdentity_WithMatchingUuids_ReturnsTrue() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = "test-uuid-123"; + session1.ServerId = 1; + session2.ServerUuid = "test-uuid-123"; + session2.ServerId = 2; // Different server ID, but UUIDs match + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.True(result); + } + + [Fact] + public void VerifyServerIdentity_WithDifferentUuids_ReturnsFalse() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = "test-uuid-123"; + session1.ServerId = 1; + session2.ServerUuid = "test-uuid-456"; + session2.ServerId = 1; // Same server ID, but UUIDs don't match + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.False(result); + } + + [Fact] + public void VerifyServerIdentity_WithMatchingServerIds_ReturnsTrue() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = null; // No UUID available + session1.ServerId = 1; + session2.ServerUuid = null; // No UUID available + session2.ServerId = 1; + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.True(result); + } + + [Fact] + public void VerifyServerIdentity_WithDifferentServerIds_ReturnsFalse() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = null; // No UUID available + session1.ServerId = 1; + session2.ServerUuid = null; // No UUID available + session2.ServerId = 2; + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.False(result); + } + + [Fact] + public void VerifyServerIdentity_WithNoIdentification_ReturnsTrue() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = null; + session1.ServerId = null; + session2.ServerUuid = null; + session2.ServerId = null; + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.True(result); // Should allow operation for backward compatibility + } + + [Fact] + public void VerifyServerIdentity_UuidTakesPrecedenceOverServerId() + { + // Arrange + var session1 = CreateServerSession(); + var session2 = CreateServerSession(); + session1.ServerUuid = "test-uuid-123"; + session1.ServerId = 1; + session2.ServerUuid = "test-uuid-456"; // Different UUID + session2.ServerId = 1; // Same server ID + + // Act + bool result = InvokeVerifyServerIdentity(session1, session2); + + // Assert + Assert.False(result); // Should use UUID comparison, not server ID + } + + private static ServerSession CreateServerSession() + { + var pool = new TestConnectionPool(); + return new ServerSession(NullLogger.Instance, pool); + } + + private static bool InvokeVerifyServerIdentity(ServerSession session1, ServerSession session2) + { + // Use reflection to call the private VerifyServerIdentity method + var method = typeof(ServerSession).GetMethod("VerifyServerIdentity", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + return (bool)method!.Invoke(session1, new object[] { session2 })!; + } + + private class TestConnectionPool : IConnectionPoolMetadata + { + public int Id => 1; + public int Generation => 1; + public ConnectionPool? ConnectionPool => null; + public int GetNewSessionId() => 1; + } +} \ No newline at end of file From 09e91c5bb71bbf08394fe4e11834c2ae54f2ba55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:29:46 +0000 Subject: [PATCH 3/7] Improve server identification handling and add integration tests Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- src/MySqlConnector/Core/ServerSession.cs | 9 ++- .../ServerIdentificationTests.cs | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 tests/IntegrationTests/ServerIdentificationTests.cs diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index 68dd7a7d3..1f5cebfeb 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -2015,9 +2015,9 @@ private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, Cancellat var reader = new ByteArrayReader(payload.Span); var length = reader.ReadLengthEncodedIntegerOrNull(); - var serverUuid = length != -1 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null; + var serverUuid = length > 0 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null; length = reader.ReadLengthEncodedIntegerOrNull(); - var serverId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); + var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); ServerUuid = serverUuid; ServerId = serverId; @@ -2047,7 +2047,7 @@ private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, Cancellat var reader = new ByteArrayReader(payload.Span); var length = reader.ReadLengthEncodedIntegerOrNull(); - var serverId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); + var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); ServerUuid = null; ServerId = serverId; @@ -2065,6 +2065,9 @@ private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, Cancellat catch (MySqlException ex) { Log.FailedToGetServerIdentification(m_logger, ex, Id); + // Set fallback values to ensure operation can continue + ServerUuid = null; + ServerId = null; } } diff --git a/tests/IntegrationTests/ServerIdentificationTests.cs b/tests/IntegrationTests/ServerIdentificationTests.cs new file mode 100644 index 000000000..1e4fd2584 --- /dev/null +++ b/tests/IntegrationTests/ServerIdentificationTests.cs @@ -0,0 +1,73 @@ +using System.Diagnostics; + +namespace IntegrationTests; + +public class ServerIdentificationTests : IClassFixture, IDisposable +{ + public ServerIdentificationTests(DatabaseFixture database) + { + m_database = database; + } + + public void Dispose() + { + } + + [SkippableFact(ServerFeatures.Timeout)] + public void CancelCommand_WithServerVerification() + { + // This test verifies that cancellation still works with server verification + using var connection = new MySqlConnection(AppConfig.ConnectionString); + connection.Open(); + + using var cmd = new MySqlCommand("SELECT SLEEP(5)", connection); + var task = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(0.5)); + cmd.Cancel(); + }); + + var stopwatch = Stopwatch.StartNew(); + TestUtilities.AssertExecuteScalarReturnsOneOrIsCanceled(cmd); + Assert.InRange(stopwatch.ElapsedMilliseconds, 250, 2500); + +#pragma warning disable xUnit1031 // Do not use blocking task operations in test method + task.Wait(); // shouldn't throw +#pragma warning restore xUnit1031 // Do not use blocking task operations in test method + + TestUtilities.LogInfo("Cancellation with server verification completed successfully"); + } + + [SkippableFact(ServerFeatures.KnownCertificateAuthority)] + public void ServerHasServerIdentification() + { + using var connection = new MySqlConnection(AppConfig.ConnectionString); + connection.Open(); + + // Test that we can query server identification manually + using var cmd = new MySqlCommand("SELECT @@server_id", connection); + var serverId = cmd.ExecuteScalar(); + Assert.NotNull(serverId); + TestUtilities.LogInfo($"Server ID: {serverId}"); + + // Test server UUID if available (MySQL 5.6+) + if (connection.ServerVersion.Version.Major > 5 || + (connection.ServerVersion.Version.Major == 5 && connection.ServerVersion.Version.Minor >= 6)) + { + try + { + using var uuidCmd = new MySqlCommand("SELECT @@server_uuid", connection); + var serverUuid = uuidCmd.ExecuteScalar(); + Assert.NotNull(serverUuid); + TestUtilities.LogInfo($"Server UUID: {serverUuid}"); + } + catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.UnknownSystemVariable) + { + // Some MySQL-compatible servers might not support server_uuid + TestUtilities.LogInfo("Server UUID not supported on this server"); + } + } + } + + private readonly DatabaseFixture m_database; +} \ No newline at end of file From 780bc2b6ba10680aef979db0385fdab0ee65a0f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:01:44 +0000 Subject: [PATCH 4/7] Fix EventIds: Add new event IDs at end instead of changing existing ones Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- src/MySqlConnector/Logging/EventIds.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MySqlConnector/Logging/EventIds.cs b/src/MySqlConnector/Logging/EventIds.cs index 234e5c240..32b247f8c 100644 --- a/src/MySqlConnector/Logging/EventIds.cs +++ b/src/MySqlConnector/Logging/EventIds.cs @@ -78,17 +78,17 @@ internal static class EventIds public const int DetectedProxy = 2150; public const int ChangingConnectionId = 2151; public const int FailedToGetConnectionId = 2152; - public const int GettingServerIdentification = 2153; - public const int RetrievedServerIdentification = 2154; - public const int FailedToGetServerIdentification = 2155; - public const int CreatingConnectionAttributes = 2156; - public const int ObtainingPasswordViaProvidePasswordCallback = 2157; - public const int FailedToObtainPassword = 2158; - public const int ConnectedTlsBasicPreliminary = 2159; - public const int ConnectedTlsDetailedPreliminary = 2160; - public const int CertificateErrorUnixSocket = 2161; - public const int CertificateErrorNoPassword = 2162; - public const int CertificateErrorValidThumbprint = 2163; + public const int CreatingConnectionAttributes = 2153; + public const int ObtainingPasswordViaProvidePasswordCallback = 2154; + public const int FailedToObtainPassword = 2155; + public const int ConnectedTlsBasicPreliminary = 2156; + public const int ConnectedTlsDetailedPreliminary = 2157; + public const int CertificateErrorUnixSocket = 2158; + public const int CertificateErrorNoPassword = 2159; + public const int CertificateErrorValidThumbprint = 2160; + public const int GettingServerIdentification = 2161; + public const int RetrievedServerIdentification = 2162; + public const int FailedToGetServerIdentification = 2163; // Command execution events, 2200-2299 public const int CannotExecuteNewCommandInState = 2200; From b0ee0f8a2570293c5bf0d8ba39c88d8ef117d968 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:17:10 +0000 Subject: [PATCH 5/7] Replace server UUID/ID verification with hostname-based verification Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- src/MySqlConnector/Core/ServerSession.cs | 130 +++++------------- src/MySqlConnector/Core/ServerVersions.cs | 3 - src/MySqlConnector/Logging/EventIds.cs | 7 +- src/MySqlConnector/Logging/Log.cs | 19 ++- .../ServerIdentificationTests.cs | 31 +---- .../ServerIdentificationTests.cs | 112 ++++----------- 6 files changed, 82 insertions(+), 220 deletions(-) diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index 1f5cebfeb..818b94696 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -47,8 +47,7 @@ public ServerSession(ILogger logger, IConnectionPoolMetadata pool) public int ActiveCommandId { get; private set; } public int CancellationTimeout { get; private set; } public int ConnectionId { get; set; } - public string? ServerUuid { get; set; } - public long? ServerId { get; set; } + public string? ServerHostname { get; set; } public byte[]? AuthPluginData { get; set; } public long CreatedTimestamp { get; } public ConnectionPool? Pool { get; } @@ -121,11 +120,21 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma // Verify server identity before executing KILL QUERY to prevent cancelling on the wrong server var killSession = killCommand.Connection!.Session; - if (!VerifyServerIdentity(killSession)) + if (!string.IsNullOrEmpty(ServerHostname) && !string.IsNullOrEmpty(killSession.ServerHostname)) { - Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerUuid, killSession.ServerUuid, ServerId, killSession.ServerId); + if (!string.Equals(ServerHostname, killSession.ServerHostname, StringComparison.Ordinal)) + { + Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerHostname, killSession.ServerHostname); + return; + } + } + else if (!string.IsNullOrEmpty(ServerHostname) || !string.IsNullOrEmpty(killSession.ServerHostname)) + { + // One session has hostname, the other doesn't - this is a potential mismatch + Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerHostname, killSession.ServerHostname); return; } + // If both sessions have no hostname, allow the operation for backward compatibility // NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation. // For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId @@ -147,26 +156,6 @@ public void AbortCancel(ICancellableCommand command) } } - private bool VerifyServerIdentity(ServerSession otherSession) - { - // If server UUID is available, use it as the primary identifier (most unique) - if (!string.IsNullOrEmpty(ServerUuid) && !string.IsNullOrEmpty(otherSession.ServerUuid)) - { - return string.Equals(ServerUuid, otherSession.ServerUuid, StringComparison.Ordinal); - } - - // Fall back to server ID if UUID is not available - if (ServerId.HasValue && otherSession.ServerId.HasValue) - { - return ServerId.Value == otherSession.ServerId.Value; - } - - // If no server identification is available, allow the operation to proceed - // This maintains backward compatibility with older MySQL versions - Log.NoServerIdentificationForVerification(m_logger, Id, otherSession.Id); - return true; - } - public bool IsCancelingQuery => m_state == State.CancelingQuery; public async Task PrepareAsync(IMySqlCommand command, IOBehavior ioBehavior, CancellationToken cancellationToken) @@ -665,8 +654,8 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella ConnectionId = newConnectionId; } - // Get server identification for KILL QUERY verification - await GetServerIdentificationAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + // Get server hostname for KILL QUERY verification + await GetServerHostnameAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); m_payloadHandler.ByteHandler.RemainingTimeout = Constants.InfiniteTimeout; return redirectionUrl; @@ -1984,76 +1973,36 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation } } - private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) + private async Task GetServerHostnameAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { - Log.GettingServerIdentification(m_logger, Id); + Log.GettingServerHostname(m_logger, Id); try { - PayloadData payload; - - // Try to get both server_uuid and server_id if server supports server_uuid (MySQL 5.6+) - if (!ServerVersion.IsMariaDb && ServerVersion.Version >= ServerVersions.SupportsServerUuid) - { - payload = SupportsQueryAttributes ? s_selectServerIdWithAttributesPayload : s_selectServerIdNoAttributesPayload; - await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); - - // column count: 2 - _ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); + var payload = SupportsQueryAttributes ? s_selectHostnameWithAttributesPayload : s_selectHostnameNoAttributesPayload; + await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); - // @@server_uuid and @@server_id columns - _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + // column count: 1 + _ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); - if (!SupportsDeprecateEof) - { - payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - _ = EofPayload.Create(payload.Span); - } + // @@hostname column + _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - // first (and only) row + if (!SupportsDeprecateEof) + { payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - - var reader = new ByteArrayReader(payload.Span); - var length = reader.ReadLengthEncodedIntegerOrNull(); - var serverUuid = length > 0 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null; - length = reader.ReadLengthEncodedIntegerOrNull(); - var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); - - ServerUuid = serverUuid; - ServerId = serverId; - - Log.RetrievedServerIdentification(m_logger, Id, serverUuid, serverId); + _ = EofPayload.Create(payload.Span); } - else - { - // Fall back to just server_id for older versions or MariaDB - payload = SupportsQueryAttributes ? s_selectServerIdOnlyWithAttributesPayload : s_selectServerIdOnlyNoAttributesPayload; - await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); - - // column count: 1 - _ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); - - // @@server_id column - _ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - if (!SupportsDeprecateEof) - { - payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - _ = EofPayload.Create(payload.Span); - } - - // first (and only) row - payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); + // first (and only) row + payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - var reader = new ByteArrayReader(payload.Span); - var length = reader.ReadLengthEncodedIntegerOrNull(); - var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?); + var reader = new ByteArrayReader(payload.Span); + var length = reader.ReadLengthEncodedIntegerOrNull(); + var hostname = length > 0 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null; - ServerUuid = null; - ServerId = serverId; + ServerHostname = hostname; - Log.RetrievedServerIdentification(m_logger, Id, null, serverId); - } + Log.RetrievedServerHostname(m_logger, Id, hostname); // OK/EOF payload payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); @@ -2064,10 +2013,9 @@ private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, Cancellat } catch (MySqlException ex) { - Log.FailedToGetServerIdentification(m_logger, ex, Id); - // Set fallback values to ensure operation can continue - ServerUuid = null; - ServerId = null; + Log.FailedToGetServerHostname(m_logger, ex, Id); + // Set fallback value to ensure operation can continue + ServerHostname = null; } } @@ -2302,10 +2250,8 @@ protected override void OnStatementBegin(int index) private static readonly PayloadData s_sleepWithAttributesPayload = QueryPayload.Create(true, "SELECT SLEEP(0) INTO @__MySqlConnector__Sleep;"u8); private static readonly PayloadData s_selectConnectionIdVersionNoAttributesPayload = QueryPayload.Create(false, "SELECT CONNECTION_ID(), VERSION();"u8); private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload.Create(true, "SELECT CONNECTION_ID(), VERSION();"u8); - private static readonly PayloadData s_selectServerIdNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_uuid, @@server_id;"u8); - private static readonly PayloadData s_selectServerIdWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_uuid, @@server_id;"u8); - private static readonly PayloadData s_selectServerIdOnlyNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_id;"u8); - private static readonly PayloadData s_selectServerIdOnlyWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_id;"u8); + private static readonly PayloadData s_selectHostnameNoAttributesPayload = QueryPayload.Create(false, "SELECT @@hostname;"u8); + private static readonly PayloadData s_selectHostnameWithAttributesPayload = QueryPayload.Create(true, "SELECT @@hostname;"u8); private readonly ILogger m_logger; #if NET9_0_OR_GREATER diff --git a/src/MySqlConnector/Core/ServerVersions.cs b/src/MySqlConnector/Core/ServerVersions.cs index 1e02fad8c..fc8e498f5 100644 --- a/src/MySqlConnector/Core/ServerVersions.cs +++ b/src/MySqlConnector/Core/ServerVersions.cs @@ -19,7 +19,4 @@ internal static class ServerVersions // https://mariadb.com/kb/en/set-statement/ public static readonly Version MariaDbSupportsPerQueryVariables = new(10, 1, 2); - - // https://dev.mysql.com/doc/refman/5.6/en/replication-options.html#sysvar_server_uuid - public static readonly Version SupportsServerUuid = new(5, 6, 0); } diff --git a/src/MySqlConnector/Logging/EventIds.cs b/src/MySqlConnector/Logging/EventIds.cs index 32b247f8c..8dab69056 100644 --- a/src/MySqlConnector/Logging/EventIds.cs +++ b/src/MySqlConnector/Logging/EventIds.cs @@ -86,9 +86,9 @@ internal static class EventIds public const int CertificateErrorUnixSocket = 2158; public const int CertificateErrorNoPassword = 2159; public const int CertificateErrorValidThumbprint = 2160; - public const int GettingServerIdentification = 2161; - public const int RetrievedServerIdentification = 2162; - public const int FailedToGetServerIdentification = 2163; + public const int GettingServerHostname = 2161; + public const int RetrievedServerHostname = 2162; + public const int FailedToGetServerHostname = 2163; // Command execution events, 2200-2299 public const int CannotExecuteNewCommandInState = 2200; @@ -112,7 +112,6 @@ internal static class EventIds public const int CancelingCommand = 2307; public const int SendingSleepToClearPendingCancellation = 2308; public const int IgnoringCancellationForDifferentServer = 2309; - public const int NoServerIdentificationForVerification = 2310; // Cached procedure events, 2400-2499 public const int GettingCachedProcedure = 2400; diff --git a/src/MySqlConnector/Logging/Log.cs b/src/MySqlConnector/Logging/Log.cs index 56c171eaa..c195d1387 100644 --- a/src/MySqlConnector/Logging/Log.cs +++ b/src/MySqlConnector/Logging/Log.cs @@ -189,20 +189,17 @@ internal static partial class Log [LoggerMessage(EventIds.FailedToGetConnectionId, LogLevel.Information, "Session {SessionId} failed to get CONNECTION_ID(), VERSION()")] public static partial void FailedToGetConnectionId(ILogger logger, Exception exception, string sessionId); - [LoggerMessage(EventIds.GettingServerIdentification, LogLevel.Debug, "Session {SessionId} getting server identification")] - public static partial void GettingServerIdentification(ILogger logger, string sessionId); + [LoggerMessage(EventIds.GettingServerHostname, LogLevel.Debug, "Session {SessionId} getting server hostname")] + public static partial void GettingServerHostname(ILogger logger, string sessionId); - [LoggerMessage(EventIds.RetrievedServerIdentification, LogLevel.Debug, "Session {SessionId} retrieved server identification: UUID={ServerUuid}, ID={ServerId}")] - public static partial void RetrievedServerIdentification(ILogger logger, string sessionId, string? serverUuid, long? serverId); + [LoggerMessage(EventIds.RetrievedServerHostname, LogLevel.Debug, "Session {SessionId} retrieved server hostname: {ServerHostname}")] + public static partial void RetrievedServerHostname(ILogger logger, string sessionId, string? serverHostname); - [LoggerMessage(EventIds.FailedToGetServerIdentification, LogLevel.Information, "Session {SessionId} failed to get server identification")] - public static partial void FailedToGetServerIdentification(ILogger logger, Exception exception, string sessionId); + [LoggerMessage(EventIds.FailedToGetServerHostname, LogLevel.Information, "Session {SessionId} failed to get server hostname")] + public static partial void FailedToGetServerHostname(ILogger logger, Exception exception, string sessionId); - [LoggerMessage(EventIds.IgnoringCancellationForDifferentServer, LogLevel.Warning, "Session {SessionId} ignoring cancellation from session {KillSessionId}: server identity mismatch (this UUID={ServerUuid}, kill UUID={KillServerUuid}, this ID={ServerId}, kill ID={KillServerId})")] - public static partial void IgnoringCancellationForDifferentServer(ILogger logger, string sessionId, string killSessionId, string? serverUuid, string? killServerUuid, long? serverId, long? killServerId); - - [LoggerMessage(EventIds.NoServerIdentificationForVerification, LogLevel.Debug, "Session {SessionId} and kill session {KillSessionId} have no server identification available for verification")] - public static partial void NoServerIdentificationForVerification(ILogger logger, string sessionId, string killSessionId); + [LoggerMessage(EventIds.IgnoringCancellationForDifferentServer, LogLevel.Warning, "Session {SessionId} ignoring cancellation from session {KillSessionId}: server hostname mismatch (this hostname={ServerHostname}, kill hostname={KillServerHostname})")] + public static partial void IgnoringCancellationForDifferentServer(ILogger logger, string sessionId, string killSessionId, string? serverHostname, string? killServerHostname); [LoggerMessage(EventIds.ClosingStreamSocket, LogLevel.Debug, "Session {SessionId} closing stream/socket")] public static partial void ClosingStreamSocket(ILogger logger, string sessionId); diff --git a/tests/IntegrationTests/ServerIdentificationTests.cs b/tests/IntegrationTests/ServerIdentificationTests.cs index 1e4fd2584..805156f69 100644 --- a/tests/IntegrationTests/ServerIdentificationTests.cs +++ b/tests/IntegrationTests/ServerIdentificationTests.cs @@ -39,34 +39,17 @@ public void CancelCommand_WithServerVerification() } [SkippableFact(ServerFeatures.KnownCertificateAuthority)] - public void ServerHasServerIdentification() + public void ServerHasServerHostname() { using var connection = new MySqlConnection(AppConfig.ConnectionString); connection.Open(); - // Test that we can query server identification manually - using var cmd = new MySqlCommand("SELECT @@server_id", connection); - var serverId = cmd.ExecuteScalar(); - Assert.NotNull(serverId); - TestUtilities.LogInfo($"Server ID: {serverId}"); - - // Test server UUID if available (MySQL 5.6+) - if (connection.ServerVersion.Version.Major > 5 || - (connection.ServerVersion.Version.Major == 5 && connection.ServerVersion.Version.Minor >= 6)) - { - try - { - using var uuidCmd = new MySqlCommand("SELECT @@server_uuid", connection); - var serverUuid = uuidCmd.ExecuteScalar(); - Assert.NotNull(serverUuid); - TestUtilities.LogInfo($"Server UUID: {serverUuid}"); - } - catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.UnknownSystemVariable) - { - // Some MySQL-compatible servers might not support server_uuid - TestUtilities.LogInfo("Server UUID not supported on this server"); - } - } + // Test that we can query server hostname + using var cmd = new MySqlCommand("SELECT @@hostname", connection); + var hostname = cmd.ExecuteScalar(); + + // Hostname might be null on some server configurations, but the query should succeed + TestUtilities.LogInfo($"Server hostname: {hostname ?? "null"}"); } private readonly DatabaseFixture m_database; diff --git a/tests/MySqlConnector.Tests/ServerIdentificationTests.cs b/tests/MySqlConnector.Tests/ServerIdentificationTests.cs index 77850d12c..22a213a67 100644 --- a/tests/MySqlConnector.Tests/ServerIdentificationTests.cs +++ b/tests/MySqlConnector.Tests/ServerIdentificationTests.cs @@ -1,117 +1,65 @@ +using Microsoft.Extensions.Logging.Abstractions; using MySqlConnector.Core; using MySqlConnector.Logging; -using Microsoft.Extensions.Logging.Abstractions; namespace MySqlConnector.Tests; -public class ServerIdentificationTests +public class ServerHostnameVerificationTests { [Fact] - public void VerifyServerIdentity_WithMatchingUuids_ReturnsTrue() + public void HostnameVerification_WithMatchingHostnames_AllowsCancellation() { // Arrange var session1 = CreateServerSession(); var session2 = CreateServerSession(); - session1.ServerUuid = "test-uuid-123"; - session1.ServerId = 1; - session2.ServerUuid = "test-uuid-123"; - session2.ServerId = 2; // Different server ID, but UUIDs match + session1.ServerHostname = "mysql-server-1"; + session2.ServerHostname = "mysql-server-1"; - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); - - // Assert - Assert.True(result); + // Act & Assert - this should not throw and should proceed with cancellation + // In a real scenario, this would be tested through the DoCancel method + Assert.Equal("mysql-server-1", session1.ServerHostname); + Assert.Equal("mysql-server-1", session2.ServerHostname); } [Fact] - public void VerifyServerIdentity_WithDifferentUuids_ReturnsFalse() + public void HostnameVerification_WithDifferentHostnames_PreventsCancellation() { // Arrange var session1 = CreateServerSession(); var session2 = CreateServerSession(); - session1.ServerUuid = "test-uuid-123"; - session1.ServerId = 1; - session2.ServerUuid = "test-uuid-456"; - session2.ServerId = 1; // Same server ID, but UUIDs don't match - - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); + session1.ServerHostname = "mysql-server-1"; + session2.ServerHostname = "mysql-server-2"; - // Assert - Assert.False(result); + // Act & Assert - this should prevent cancellation + Assert.NotEqual(session1.ServerHostname, session2.ServerHostname); } [Fact] - public void VerifyServerIdentity_WithMatchingServerIds_ReturnsTrue() + public void HostnameVerification_WithNullHostnames_AllowsCancellationForBackwardCompatibility() { // Arrange var session1 = CreateServerSession(); var session2 = CreateServerSession(); - session1.ServerUuid = null; // No UUID available - session1.ServerId = 1; - session2.ServerUuid = null; // No UUID available - session2.ServerId = 1; + session1.ServerHostname = null; + session2.ServerHostname = null; - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); - - // Assert - Assert.True(result); + // Act & Assert - should allow cancellation for backward compatibility + Assert.Null(session1.ServerHostname); + Assert.Null(session2.ServerHostname); } [Fact] - public void VerifyServerIdentity_WithDifferentServerIds_ReturnsFalse() + public void HostnameVerification_WithOneNullHostname_PreventsCancellation() { // Arrange var session1 = CreateServerSession(); var session2 = CreateServerSession(); - session1.ServerUuid = null; // No UUID available - session1.ServerId = 1; - session2.ServerUuid = null; // No UUID available - session2.ServerId = 2; - - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); + session1.ServerHostname = "mysql-server-1"; + session2.ServerHostname = null; - // Assert - Assert.False(result); - } - - [Fact] - public void VerifyServerIdentity_WithNoIdentification_ReturnsTrue() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerUuid = null; - session1.ServerId = null; - session2.ServerUuid = null; - session2.ServerId = null; - - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); - - // Assert - Assert.True(result); // Should allow operation for backward compatibility - } - - [Fact] - public void VerifyServerIdentity_UuidTakesPrecedenceOverServerId() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerUuid = "test-uuid-123"; - session1.ServerId = 1; - session2.ServerUuid = "test-uuid-456"; // Different UUID - session2.ServerId = 1; // Same server ID - - // Act - bool result = InvokeVerifyServerIdentity(session1, session2); - - // Assert - Assert.False(result); // Should use UUID comparison, not server ID + // Act & Assert - one has hostname, other doesn't - should prevent cancellation + Assert.NotNull(session1.ServerHostname); + Assert.Null(session2.ServerHostname); } private static ServerSession CreateServerSession() @@ -120,14 +68,6 @@ private static ServerSession CreateServerSession() return new ServerSession(NullLogger.Instance, pool); } - private static bool InvokeVerifyServerIdentity(ServerSession session1, ServerSession session2) - { - // Use reflection to call the private VerifyServerIdentity method - var method = typeof(ServerSession).GetMethod("VerifyServerIdentity", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - return (bool)method!.Invoke(session1, new object[] { session2 })!; - } - private class TestConnectionPool : IConnectionPoolMetadata { public int Id => 1; From d2619e9d35546a790d2e2664b8bc32209080e3fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:33:29 +0000 Subject: [PATCH 6/7] Remove TestUtilities.LogInfo calls from integration tests Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- tests/IntegrationTests/ServerIdentificationTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/IntegrationTests/ServerIdentificationTests.cs b/tests/IntegrationTests/ServerIdentificationTests.cs index 805156f69..d893fad9d 100644 --- a/tests/IntegrationTests/ServerIdentificationTests.cs +++ b/tests/IntegrationTests/ServerIdentificationTests.cs @@ -34,8 +34,6 @@ public void CancelCommand_WithServerVerification() #pragma warning disable xUnit1031 // Do not use blocking task operations in test method task.Wait(); // shouldn't throw #pragma warning restore xUnit1031 // Do not use blocking task operations in test method - - TestUtilities.LogInfo("Cancellation with server verification completed successfully"); } [SkippableFact(ServerFeatures.KnownCertificateAuthority)] @@ -49,7 +47,6 @@ public void ServerHasServerHostname() var hostname = cmd.ExecuteScalar(); // Hostname might be null on some server configurations, but the query should succeed - TestUtilities.LogInfo($"Server hostname: {hostname ?? "null"}"); } private readonly DatabaseFixture m_database; From ae5439812e4183cafe26226f98f67b94a213c019 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:35:57 +0000 Subject: [PATCH 7/7] Delete unnecessary ServerIdentificationTests unit test class Co-authored-by: bgrainger <188129+bgrainger@users.noreply.github.com> --- .../ServerIdentificationTests.cs | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 tests/MySqlConnector.Tests/ServerIdentificationTests.cs diff --git a/tests/MySqlConnector.Tests/ServerIdentificationTests.cs b/tests/MySqlConnector.Tests/ServerIdentificationTests.cs deleted file mode 100644 index 22a213a67..000000000 --- a/tests/MySqlConnector.Tests/ServerIdentificationTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.Extensions.Logging.Abstractions; -using MySqlConnector.Core; -using MySqlConnector.Logging; - -namespace MySqlConnector.Tests; - -public class ServerHostnameVerificationTests -{ - [Fact] - public void HostnameVerification_WithMatchingHostnames_AllowsCancellation() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerHostname = "mysql-server-1"; - session2.ServerHostname = "mysql-server-1"; - - // Act & Assert - this should not throw and should proceed with cancellation - // In a real scenario, this would be tested through the DoCancel method - Assert.Equal("mysql-server-1", session1.ServerHostname); - Assert.Equal("mysql-server-1", session2.ServerHostname); - } - - [Fact] - public void HostnameVerification_WithDifferentHostnames_PreventsCancellation() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerHostname = "mysql-server-1"; - session2.ServerHostname = "mysql-server-2"; - - // Act & Assert - this should prevent cancellation - Assert.NotEqual(session1.ServerHostname, session2.ServerHostname); - } - - [Fact] - public void HostnameVerification_WithNullHostnames_AllowsCancellationForBackwardCompatibility() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerHostname = null; - session2.ServerHostname = null; - - // Act & Assert - should allow cancellation for backward compatibility - Assert.Null(session1.ServerHostname); - Assert.Null(session2.ServerHostname); - } - - [Fact] - public void HostnameVerification_WithOneNullHostname_PreventsCancellation() - { - // Arrange - var session1 = CreateServerSession(); - var session2 = CreateServerSession(); - session1.ServerHostname = "mysql-server-1"; - session2.ServerHostname = null; - - // Act & Assert - one has hostname, other doesn't - should prevent cancellation - Assert.NotNull(session1.ServerHostname); - Assert.Null(session2.ServerHostname); - } - - private static ServerSession CreateServerSession() - { - var pool = new TestConnectionPool(); - return new ServerSession(NullLogger.Instance, pool); - } - - private class TestConnectionPool : IConnectionPoolMetadata - { - public int Id => 1; - public int Generation => 1; - public ConnectionPool? ConnectionPool => null; - public int GetNewSessionId() => 1; - } -} \ No newline at end of file