Skip to content

Commit f74ce0b

Browse files
committed
Implement core code to support multiple compression methods.
1 parent 8107efe commit f74ce0b

File tree

9 files changed

+66
-16
lines changed

9 files changed

+66
-16
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace MySqlConnector.Core;
2+
3+
internal enum CompressionMethod
4+
{
5+
None,
6+
Zlib,
7+
Zstandard,
8+
}

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,9 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
451451
ServerVersion = new(initialHandshake.ServerVersion);
452452
ConnectionId = initialHandshake.ConnectionId;
453453
AuthPluginData = initialHandshake.AuthPluginData;
454-
m_useCompression = cs.UseCompression && (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) != 0;
454+
m_compressionMethod = !cs.UseCompression ? CompressionMethod.None :
455+
((initialHandshake.ProtocolCapabilities & ProtocolCapabilities.ZstandardCompressionAlgorithm) != 0 && connection.ZstandardPlugin is not null) ? CompressionMethod.Zstandard :
456+
((initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) != 0) ? CompressionMethod.Zlib : CompressionMethod.None;
455457
CancellationTimeout = cs.CancellationTimeout;
456458
UserID = cs.UserID;
457459

@@ -483,7 +485,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
483485
else
484486
{
485487
// pipelining is not currently compatible with compression
486-
m_supportsPipelining = !cs.UseCompression && cs.Pipelining is not false;
488+
m_supportsPipelining = m_compressionMethod == CompressionMethod.None && cs.Pipelining is not false;
487489

488490
// for pipelining, concatenate reset connection and SET NAMES query into one buffer
489491
if (m_supportsPipelining)
@@ -500,7 +502,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
500502
}
501503
}
502504

503-
Log.SessionMadeConnection(m_logger, Id, ServerVersion.OriginalString, ConnectionId, m_useCompression, m_supportsConnectionAttributes, SupportsDeprecateEof, SupportsCachedPreparedMetadata, serverSupportsSsl, SupportsSessionTrack, m_supportsPipelining, SupportsQueryAttributes);
505+
Log.SessionMadeConnection(m_logger, Id, ServerVersion.OriginalString, ConnectionId, m_compressionMethod != CompressionMethod.None, m_supportsConnectionAttributes, SupportsDeprecateEof, SupportsCachedPreparedMetadata, serverSupportsSsl, SupportsSessionTrack, m_supportsPipelining, SupportsQueryAttributes);
504506

505507
if (cs.SslMode != MySqlSslMode.None && (cs.SslMode != MySqlSslMode.Preferred || serverSupportsSsl))
506508
{
@@ -517,7 +519,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
517519
cs.ConnectionAttributes = CreateConnectionAttributes(cs.ApplicationName);
518520

519521
var password = GetPassword(cs, connection);
520-
using (var handshakeResponsePayload = HandshakeResponse41Payload.Create(initialHandshake, cs, password, m_useCompression, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
522+
using (var handshakeResponsePayload = HandshakeResponse41Payload.Create(initialHandshake, cs, password, m_compressionMethod, connection.ZstandardPlugin?.CompressionLevel, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
521523
await SendReplyAsync(handshakeResponsePayload, ioBehavior, cancellationToken).ConfigureAwait(false);
522524
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
523525

@@ -570,8 +572,10 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
570572

571573
var redirectionUrl = ok.RedirectionUrl;
572574

573-
if (m_useCompression)
575+
if (m_compressionMethod == CompressionMethod.Zlib)
574576
m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler);
577+
else if (m_compressionMethod == CompressionMethod.Zstandard)
578+
m_payloadHandler = connection.ZstandardPlugin!.CreatePayloadHandler(m_payloadHandler.ByteHandler);
575579

576580
// 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)
577581
if (ok.NewCharacterSet != (ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? CharacterSet.Utf8Mb4Binary : CharacterSet.Utf8Mb3Binary))
@@ -1632,7 +1636,7 @@ caCertificateChain is not null &&
16321636

16331637
var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull;
16341638

1635-
using (var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_useCompression, m_characterSet))
1639+
using (var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_compressionMethod, m_characterSet))
16361640
await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false);
16371641

16381642
var clientAuthenticationOptions = new SslClientAuthenticationOptions
@@ -2129,7 +2133,7 @@ protected override void OnStatementBegin(int index)
21292133
private SslStream? m_sslStream;
21302134
private X509Certificate2? m_clientCertificate;
21312135
private IPayloadHandler? m_payloadHandler;
2132-
private bool m_useCompression;
2136+
private CompressionMethod m_compressionMethod;
21332137
private bool m_isSecureConnection;
21342138
private bool m_supportsConnectionAttributes;
21352139
private bool m_supportsPipelining;

src/MySqlConnector/MySqlConnection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Extensions.Logging;
1212
using MySqlConnector.Core;
1313
using MySqlConnector.Logging;
14+
using MySqlConnector.Plugins;
1415
using MySqlConnector.Protocol;
1516
using MySqlConnector.Protocol.Payloads;
1617
using MySqlConnector.Protocol.Serialization;
@@ -979,6 +980,7 @@ internal void Cancel(ICancellableCommand command, int commandId, bool isCancel)
979980

980981
internal MySqlTransaction? CurrentTransaction { get; set; }
981982
internal MySqlConnectorLoggingConfiguration LoggingConfiguration { get; }
983+
internal ZstandardPlugin? ZstandardPlugin { get; set; }
982984
internal bool AllowLoadLocalInfile => GetInitializedConnectionSettings().AllowLoadLocalInfile;
983985
internal bool AllowUserVariables => GetInitializedConnectionSettings().AllowUserVariables;
984986
internal bool AllowZeroDateTime => GetInitializedConnectionSettings().AllowZeroDateTime;

src/MySqlConnector/MySqlConnector.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
<ItemGroup>
3737
<InternalsVisibleTo Include="MySqlConnector.DependencyInjection.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
38+
<InternalsVisibleTo Include="MySqlConnector.Encryption" Key="0024000004800000940000000602000000240000525341310004000001000100e57cf9527549be513974d12b6730ff1db99b10a1498bb424ef0a14f0441f7043c5159f51629255107b314c710bf29463e1cfa4585732f560c3ef25207c7dfa57004d28927078a28b0ab7fa534403c18f2d5aaeda506a3d276c26b3bcc62f7fc8efe9aaf3e68b14b839ad10f10f42db834d170c96b1da9fc463231afe73bbaabe" />
39+
<InternalsVisibleTo Include="MySqlConnector.Zstandard" Key="0024000004800000940000000602000000240000525341310004000001000100e57cf9527549be513974d12b6730ff1db99b10a1498bb424ef0a14f0441f7043c5159f51629255107b314c710bf29463e1cfa4585732f560c3ef25207c7dfa57004d28927078a28b0ab7fa534403c18f2d5aaeda506a3d276c26b3bcc62f7fc8efe9aaf3e68b14b839ad10f10f42db834d170c96b1da9fc463231afe73bbaabe" />
3840
<InternalsVisibleTo Include="MySqlConnector.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
3941
<InternalsVisibleTo Include="IntegrationTests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
4042
<Using Include="System.Data" />

src/MySqlConnector/MySqlDataSource.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Logging;
44
using MySqlConnector.Core;
55
using MySqlConnector.Logging;
6+
using MySqlConnector.Plugins;
67
using MySqlConnector.Protocol.Serialization;
78

89
namespace MySqlConnector;
@@ -18,7 +19,7 @@ public sealed class MySqlDataSource : DbDataSource
1819
/// <param name="connectionString">The connection string for the MySQL Server. This parameter is required.</param>
1920
/// <exception cref="ArgumentNullException">Thrown if <paramref name="connectionString"/> is <c>null</c>.</exception>
2021
public MySqlDataSource(string connectionString)
21-
: this(connectionString ?? throw new ArgumentNullException(nameof(connectionString)), MySqlConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, default, default)
22+
: this(connectionString ?? throw new ArgumentNullException(nameof(connectionString)), MySqlConnectorLoggingConfiguration.NullConfiguration, null, null, null, null, default, default, default)
2223
{
2324
}
2425

@@ -29,14 +30,16 @@ internal MySqlDataSource(string connectionString,
2930
RemoteCertificateValidationCallback? remoteCertificateValidationCallback,
3031
Func<MySqlProvidePasswordContext, CancellationToken, ValueTask<string>>? periodicPasswordProvider,
3132
TimeSpan periodicPasswordProviderSuccessRefreshInterval,
32-
TimeSpan periodicPasswordProviderFailureRefreshInterval)
33+
TimeSpan periodicPasswordProviderFailureRefreshInterval,
34+
ZstandardPlugin? zstandardPlugin)
3335
{
3436
m_connectionString = connectionString;
3537
LoggingConfiguration = loggingConfiguration;
3638
Name = name;
3739
m_clientCertificatesCallback = clientCertificatesCallback;
3840
m_remoteCertificateValidationCallback = remoteCertificateValidationCallback;
3941
m_logger = loggingConfiguration.DataSourceLogger;
42+
m_zstandardPlugin = zstandardPlugin;
4043

4144
Pool = ConnectionPool.CreatePool(m_connectionString, LoggingConfiguration, name);
4245
m_id = Interlocked.Increment(ref s_lastId);
@@ -221,6 +224,7 @@ private string ProvidePasswordFromInitialRefreshTask(MySqlProvidePasswordContext
221224
private readonly Func<MySqlProvidePasswordContext, CancellationToken, ValueTask<string>>? m_periodicPasswordProvider;
222225
private readonly TimeSpan m_periodicPasswordProviderSuccessRefreshInterval;
223226
private readonly TimeSpan m_periodicPasswordProviderFailureRefreshInterval;
227+
private readonly ZstandardPlugin? m_zstandardPlugin;
224228
private readonly MySqlProvidePasswordContext? m_providePasswordContext;
225229
private readonly CancellationTokenSource? m_passwordProviderTimerCancellationTokenSource;
226230
private readonly Timer? m_passwordProviderTimer;

src/MySqlConnector/MySqlDataSourceBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Security.Cryptography.X509Certificates;
33
using Microsoft.Extensions.Logging;
44
using MySqlConnector.Logging;
5+
using MySqlConnector.Plugins;
56

67
namespace MySqlConnector;
78

@@ -102,7 +103,8 @@ public MySqlDataSource Build()
102103
m_remoteCertificateValidationCallback,
103104
m_periodicPasswordProvider,
104105
m_periodicPasswordProviderSuccessRefreshInterval,
105-
m_periodicPasswordProviderFailureRefreshInterval
106+
m_periodicPasswordProviderFailureRefreshInterval,
107+
ZstandardPlugin
106108
);
107109
}
108110

@@ -111,6 +113,8 @@ public MySqlDataSource Build()
111113
/// </summary>
112114
public MySqlConnectionStringBuilder ConnectionStringBuilder { get; }
113115

116+
internal ZstandardPlugin? ZstandardPlugin { get; set; }
117+
114118
private ILoggerFactory? m_loggerFactory;
115119
private string? m_name;
116120
private Func<X509CertificateCollection, ValueTask>? m_clientCertificatesCallback;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using MySqlConnector.Protocol.Serialization;
2+
3+
namespace MySqlConnector.Plugins;
4+
5+
internal abstract class ZstandardPlugin
6+
{
7+
public abstract IPayloadHandler CreatePayloadHandler(IByteHandler byteHandler);
8+
public abstract int CompressionLevel { get; }
9+
}

src/MySqlConnector/Protocol/Payloads/HandshakeResponse41Payload.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace MySqlConnector.Protocol.Payloads;
55

66
internal static class HandshakeResponse41Payload
77
{
8-
private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, bool useCompression, CharacterSet characterSet, ProtocolCapabilities additionalCapabilities = 0)
8+
private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, CompressionMethod compressionMethod, CharacterSet characterSet, ProtocolCapabilities additionalCapabilities = 0)
99
{
1010
var writer = new ByteBufferWriter();
1111

@@ -22,10 +22,11 @@ private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities s
2222
(cs.AllowLoadLocalInfile ? ProtocolCapabilities.LocalFiles : 0) |
2323
(string.IsNullOrWhiteSpace(cs.Database) ? 0 : ProtocolCapabilities.ConnectWithDatabase) |
2424
(cs.UseAffectedRows ? 0 : ProtocolCapabilities.FoundRows) |
25-
(useCompression ? ProtocolCapabilities.Compress : ProtocolCapabilities.None) |
25+
(compressionMethod == CompressionMethod.Zlib ? ProtocolCapabilities.Compress : ProtocolCapabilities.None) |
2626
(serverCapabilities & ProtocolCapabilities.ConnectionAttributes) |
2727
(serverCapabilities & ProtocolCapabilities.SessionTrack) |
2828
(serverCapabilities & ProtocolCapabilities.DeprecateEof) |
29+
(compressionMethod == CompressionMethod.Zstandard ? ProtocolCapabilities.ZstandardCompressionAlgorithm : 0) |
2930
(serverCapabilities & ProtocolCapabilities.QueryAttributes) |
3031
(serverCapabilities & ProtocolCapabilities.MariaDbCacheMetadata) |
3132
additionalCapabilities;
@@ -51,13 +52,13 @@ private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities s
5152
return writer;
5253
}
5354

54-
public static PayloadData CreateWithSsl(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, bool useCompression, CharacterSet characterSet) =>
55-
CreateCapabilitiesPayload(serverCapabilities, cs, useCompression, characterSet, ProtocolCapabilities.Ssl).ToPayloadData();
55+
public static PayloadData CreateWithSsl(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, CompressionMethod compressionMethod, CharacterSet characterSet) =>
56+
CreateCapabilitiesPayload(serverCapabilities, cs, compressionMethod, characterSet, ProtocolCapabilities.Ssl).ToPayloadData();
5657

57-
public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSettings cs, string password, bool useCompression, CharacterSet characterSet, byte[]? connectionAttributes)
58+
public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSettings cs, string password, CompressionMethod compressionMethod, int? compressionLevel, CharacterSet characterSet, byte[]? connectionAttributes)
5859
{
5960
// TODO: verify server capabilities
60-
var writer = CreateCapabilitiesPayload(handshake.ProtocolCapabilities, cs, useCompression, characterSet);
61+
var writer = CreateCapabilitiesPayload(handshake.ProtocolCapabilities, cs, compressionMethod, characterSet);
6162
writer.WriteNullTerminatedString(cs.UserID);
6263
var authenticationResponse = AuthenticationUtility.CreateAuthenticationResponse(handshake.AuthPluginData, password);
6364
writer.Write((byte) authenticationResponse.Length);
@@ -72,6 +73,10 @@ public static PayloadData Create(InitialHandshakePayload handshake, ConnectionSe
7273
if (connectionAttributes is not null)
7374
writer.Write(connectionAttributes);
7475

76+
// Zstandard compression level
77+
if (compressionMethod == CompressionMethod.Zstandard)
78+
writer.Write((byte) (compressionLevel ?? 10));
79+
7580
return writer.ToPayloadData();
7681
}
7782
}

src/MySqlConnector/Protocol/ProtocolCapabilities.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ internal enum ProtocolCapabilities : ulong
128128
/// </summary>
129129
DeprecateEof = 0x100_0000,
130130

131+
/// <summary>
132+
/// The client can handle optional metadata information in the resultset.
133+
/// </summary>
134+
/// <remarks>Corresponds to CLIENT_OPTIONAL_RESULTSET_METADATA.</remarks>
135+
ClientOptionalResultsetMetadata = 0x200_0000,
136+
137+
/// <summary>
138+
/// The client supports the Zstandard compression algorithm.
139+
/// </summary>
140+
/// <remarks>Corresponds to CLIENT_ZSTD_COMPRESSION_ALGORITHM.</remarks>
141+
ZstandardCompressionAlgorithm = 0x400_0000,
142+
131143
/// <summary>
132144
/// Supports query attributes (CLIENT_QUERY_ATTRIBUTES).
133145
/// </summary>

0 commit comments

Comments
 (0)