Skip to content

Commit

Permalink
Merge pull request #1497 from mysql-net/avoid-set-names
Browse files Browse the repository at this point in the history
Avoid SET NAMES commands if not needed. For MariaDB >= 11.5, session variables are sent in the initial OK Packet, permitting MySqlConnector to avoid initial SET NAMES query, since charset is already known.
  • Loading branch information
bgrainger authored Jul 17, 2024
2 parents e6bb115 + 3f50c14 commit aeaf900
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 46 deletions.
7 changes: 7 additions & 0 deletions src/MySqlConnector/Core/IServerCapabilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MySqlConnector.Core;

internal interface IServerCapabilities
{
bool SupportsDeprecateEof { get; }
bool SupportsSessionTrack { get; }
}
6 changes: 3 additions & 3 deletions src/MySqlConnector/Core/ResultSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task ReadResultSetHeaderAsync(IOBehavior ioBehavior)
var firstByte = payload.HeaderByte;
if (firstByte == OkPayload.Signature)
{
var ok = OkPayload.Create(payload.Span, Session.SupportsDeprecateEof, Session.SupportsSessionTrack);
var ok = OkPayload.Create(payload.Span, Session);

// if we've read a result set header then this is a SELECT statement, so we shouldn't overwrite RecordsAffected
// (which should be -1 for SELECT) unless the server reports a non-zero count
Expand Down Expand Up @@ -252,9 +252,9 @@ public async Task<bool> ReadAsync(IOBehavior ioBehavior, CancellationToken cance

if (payload.HeaderByte == EofPayload.Signature)
{
if (Session.SupportsDeprecateEof && OkPayload.IsOk(payload.Span, Session.SupportsDeprecateEof))
if (Session.SupportsDeprecateEof && OkPayload.IsOk(payload.Span, Session))
{
var ok = OkPayload.Create(payload.Span, Session.SupportsDeprecateEof, Session.SupportsSessionTrack);
var ok = OkPayload.Create(payload.Span, Session);
BufferState = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData;
return null;
}
Expand Down
43 changes: 27 additions & 16 deletions src/MySqlConnector/Core/ServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace MySqlConnector.Core;

#pragma warning disable CA1001 // Types that own disposable fields should be disposable

internal sealed partial class ServerSession
internal sealed partial class ServerSession : IServerCapabilities
{
public ServerSession(ILogger logger)
: this(logger, null, 0, Interlocked.Increment(ref s_lastId))
Expand Down Expand Up @@ -320,7 +320,7 @@ public void FinishQuerying()
SendAsync(payload, IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
payload = ReceiveReplyAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
#pragma warning restore CA2012
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);
}

lock (m_lock)
Expand Down Expand Up @@ -532,19 +532,30 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
payload = await SwitchAuthenticationAsync(cs, password, payload, ioBehavior, cancellationToken).ConfigureAwait(false);
}

var ok = OkPayload.Create(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
var ok = OkPayload.Create(payload.Span, this);
var statusInfo = ok.StatusInfo;

if (m_useCompression)
m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler);

// set 'collation_connection' to the server default
await SendAsync(m_setNamesPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
// 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);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, this);
}

if (ShouldGetRealServerDetails(cs))
{
await GetRealServerDetailsAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
}
else if (ok.NewConnectionId is int newConnectionId && newConnectionId != ConnectionId)
{
Log.ChangingConnectionId(m_logger, Id, ConnectionId, newConnectionId, ServerVersion.OriginalString, ServerVersion.OriginalString);
ConnectionId = newConnectionId;
}

m_payloadHandler.ByteHandler.RemainingTimeout = Constants.InfiniteTimeout;
return statusInfo;
Expand Down Expand Up @@ -584,18 +595,18 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, MySqlConn

// read two OK replies
payload = await ReceiveReplyAsync(1, ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);

payload = await ReceiveReplyAsync(1, ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);

return true;
}

Log.SendingResetConnectionRequest(m_logger, Id, ServerVersion.OriginalString);
await SendAsync(ResetConnectionPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);
}
else
{
Expand All @@ -619,13 +630,13 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, MySqlConn
Log.OptimisticReauthenticationFailed(m_logger, Id);
payload = await SwitchAuthenticationAsync(cs, password, payload, ioBehavior, cancellationToken).ConfigureAwait(false);
}
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);
}

// set 'collation_connection' to the server default
await SendAsync(m_setNamesPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);

return true;
}
Expand Down Expand Up @@ -684,7 +695,7 @@ private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs,
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);

// OK payload can be sent immediately (e.g., if password is empty) bypassing even the fast authentication path
if (OkPayload.IsOk(payload.Span, SupportsDeprecateEof))
if (OkPayload.IsOk(payload.Span, this))
return payload;

var cachingSha2ServerResponsePayload = CachingSha2ServerResponsePayload.Create(payload.Span);
Expand Down Expand Up @@ -824,7 +835,7 @@ public async ValueTask<bool> TryPingAsync(bool logInfo, IOBehavior ioBehavior, C
Log.PingingServer(m_logger, Id);
await SendAsync(PingPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
OkPayload.Verify(payload.Span, this);
Log.SuccessfullyPingedServer(m_logger, logInfo ? LogLevel.Information : LogLevel.Trace, Id);
return true;
}
Expand Down Expand Up @@ -1662,8 +1673,8 @@ static void ReadRow(ReadOnlySpan<byte> span, out int? connectionId, out ServerVe

// OK/EOF payload
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
if (OkPayload.IsOk(payload.Span, SupportsDeprecateEof))
OkPayload.Verify(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
if (OkPayload.IsOk(payload.Span, this))
OkPayload.Verify(payload.Span, this);
else
EofPayload.Create(payload.Span);

Expand Down
14 changes: 8 additions & 6 deletions src/MySqlConnector/MySqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,23 +161,23 @@ private async ValueTask<MySqlTransaction> BeginTransactionAsync(IsolationLevel i

// read the two OK replies
var payload = await m_session.ReceiveReplyAsync(1, ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, m_session.SupportsDeprecateEof, m_session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, m_session);

payload = await m_session.ReceiveReplyAsync(1, ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, m_session.SupportsDeprecateEof, m_session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, m_session);
}
else
{
// send the two packets separately
await m_session.SendAsync(new Protocol.PayloadData(startTransactionPayload.Slice(4, startTransactionPayload.Span[0])), ioBehavior, cancellationToken).ConfigureAwait(false);

var payload = await m_session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, m_session.SupportsDeprecateEof, m_session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, m_session);

await m_session.SendAsync(new Protocol.PayloadData(startTransactionPayload.Slice(8 + startTransactionPayload.Span[0], startTransactionPayload.Span[startTransactionPayload.Span[0] + 4])), ioBehavior, cancellationToken).ConfigureAwait(false);

payload = await m_session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, m_session.SupportsDeprecateEof, m_session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, m_session);
}

var transaction = new MySqlTransaction(this, isolationLevel, m_transactionLogger);
Expand Down Expand Up @@ -487,7 +487,9 @@ private async Task ChangeDatabaseAsync(IOBehavior ioBehavior, string databaseNam
using (var initDatabasePayload = InitDatabasePayload.Create(databaseName))
await m_session!.SendAsync(initDatabasePayload, ioBehavior, cancellationToken).ConfigureAwait(false);
var payload = await m_session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, m_session.SupportsDeprecateEof, m_session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, m_session);

// for non session tracking servers
m_session.DatabaseOverride = databaseName;
}

Expand Down Expand Up @@ -603,7 +605,7 @@ public async ValueTask ResetConnectionAsync(CancellationToken cancellationToken
Log.ResettingConnection(m_logger, session.Id);
await session.SendAsync(ResetConnectionPayload.Instance, AsyncIOBehavior, cancellationToken).ConfigureAwait(false);
var payload = await session.ReceiveReplyAsync(AsyncIOBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Verify(payload.Span, session.SupportsDeprecateEof, session.SupportsSessionTrack);
OkPayload.Verify(payload.Span, session);
}

[AllowNull]
Expand Down
Loading

0 comments on commit aeaf900

Please sign in to comment.