Skip to content

Commit eedf270

Browse files
committed
Use server default collation. Fixes #626
1 parent d38be8b commit eedf270

File tree

3 files changed

+80
-8
lines changed

3 files changed

+80
-8
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
314314
m_supportsSessionTrack = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SessionTrack) != 0;
315315
var serverSupportsSsl = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Ssl) != 0;
316316
m_characterSet = ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? CharacterSet.Utf8Mb4GeneralCaseInsensitive : CharacterSet.Utf8GeneralCaseInsensitive;
317+
m_setNamesPayload = ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? s_setNamesUtf8mb4Payload : s_setNamesUtf8Payload;
317318

318319
Log.Info("Session{0} made connection; ServerVersion={1}; ConnectionId={2}; Compression={3}; Attributes={4}; DeprecateEof={5}; Ssl={6}; SessionTrack={7}",
319320
m_logArguments[0], ServerVersion.OriginalString, ConnectionId,
@@ -370,6 +371,11 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
370371
if (m_useCompression)
371372
m_payloadHandler = new CompressedPayloadHandler(m_payloadHandler.ByteHandler);
372373

374+
// set 'collation_connection' to the server default
375+
await SendAsync(m_setNamesPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
376+
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
377+
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
378+
373379
if (ShouldGetRealServerDetails())
374380
await GetRealServerDetailsAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
375381
}
@@ -394,16 +400,12 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
394400
// clear all prepared statements; resetting the connection will clear them on the server
395401
ClearPreparedStatements();
396402

403+
PayloadData payload;
397404
if (DatabaseOverride is null && (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0 || ServerVersion.MariaDbVersion?.CompareTo(ServerVersions.MariaDbSupportsResetConnection) >= 0))
398405
{
399406
m_logArguments[1] = ServerVersion.OriginalString;
400407
Log.Debug("Session{0} ServerVersion={1} supports reset connection; sending reset connection request", m_logArguments);
401408
await SendAsync(ResetConnectionPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
402-
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
403-
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
404-
405-
// the "reset connection" packet also resets the connection charset, so we need to change that back to our default
406-
await SendAsync(s_setNamesUtf8mb4Payload, ioBehavior, cancellationToken).ConfigureAwait(false);
407409
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
408410
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
409411
}
@@ -424,7 +426,7 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
424426
var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
425427
using (var changeUserPayload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
426428
await SendAsync(changeUserPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
427-
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
429+
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
428430
if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
429431
{
430432
Log.Debug("Session{0} optimistic reauthentication failed; logging in again", m_logArguments);
@@ -433,6 +435,11 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
433435
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
434436
}
435437

438+
// set 'collation_connection' to the server default
439+
await SendAsync(m_setNamesPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
440+
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
441+
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
442+
436443
return true;
437444
}
438445
catch (IOException ex)
@@ -1393,7 +1400,8 @@ private enum State
13931400
static ReadOnlySpan<byte> BeginCertificateBytes => new byte[] { 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 67, 69, 82, 84, 73, 70, 73, 67, 65, 84, 69, 45, 45, 45, 45, 45 }; // -----BEGIN CERTIFICATE-----
13941401
static int s_lastId;
13951402
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(ServerSession));
1396-
static readonly PayloadData s_setNamesUtf8mb4Payload = QueryPayload.Create("SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;");
1403+
static readonly PayloadData s_setNamesUtf8Payload = QueryPayload.Create("SET NAMES utf8;");
1404+
static readonly PayloadData s_setNamesUtf8mb4Payload = QueryPayload.Create("SET NAMES utf8mb4;");
13971405

13981406
readonly object m_lock;
13991407
readonly object[] m_logArguments;
@@ -1412,6 +1420,7 @@ private enum State
14121420
bool m_supportsDeprecateEof;
14131421
bool m_supportsSessionTrack;
14141422
CharacterSet m_characterSet;
1423+
PayloadData m_setNamesPayload;
14151424
Dictionary<string, PreparedStatements> m_preparedStatements;
14161425
}
14171426
}

tests/MySqlConnector.Tests/FakeMySqlServerConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public async Task RunAsync(TcpClient client, CancellationToken token)
6565
case CommandKind.Query:
6666
var query = Encoding.UTF8.GetString(bytes, 1, bytes.Length - 1);
6767
Match match;
68-
if (query == "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;")
68+
if (query == "SET NAMES utf8mb4;")
6969
{
7070
await SendAsync(stream, 1, WriteOk);
7171
}

tests/SideBySide/CharacterSetTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System.Linq;
12
using Dapper;
3+
using MySql.Data.MySqlClient;
24
#if !BASELINE
35
using MySqlConnector.Protocol;
46
using MySqlConnector.Protocol.Serialization;
@@ -31,6 +33,67 @@ public void MaxLength()
3133
}
3234
#endif
3335

36+
[Theory]
37+
[InlineData(false)]
38+
[InlineData(true)]
39+
public void IllegalMixOfCollations(bool reopenConnection)
40+
{
41+
var csb = AppConfig.CreateConnectionStringBuilder();
42+
csb.AllowUserVariables = true;
43+
using (var connection = new MySqlConnection(csb.ConnectionString))
44+
{
45+
connection.Open();
46+
connection.Execute(@"
47+
DROP TABLE IF EXISTS mix_collations;
48+
CREATE TABLE mix_collations (
49+
id int(11) NOT NULL AUTO_INCREMENT,
50+
test_col varchar(10) DEFAULT NULL,
51+
PRIMARY KEY (id),
52+
KEY ix_test (test_col)
53+
);
54+
INSERT INTO mix_collations (test_col)
55+
VALUES ('a'), ('b'), ('c'), ('d'), ('e'), ('f'), ('g'), ('h'), ('i'), ('j');");
56+
57+
if (reopenConnection)
58+
{
59+
connection.Close();
60+
connection.Open();
61+
}
62+
63+
using (var reader = connection.ExecuteReader(@"
64+
SET @param = 'B';
65+
SELECT * FROM mix_collations a WHERE a.test_col = @param"))
66+
{
67+
Assert.True(reader.Read());
68+
}
69+
}
70+
}
71+
72+
[Theory]
73+
[InlineData(false)]
74+
[InlineData(true)]
75+
public void CollationConnection(bool reopenConnection)
76+
{
77+
var csb = AppConfig.CreateConnectionStringBuilder();
78+
#if BASELINE
79+
csb.CharacterSet = "utf8mb4";
80+
#endif
81+
using (var connection = new MySqlConnection(csb.ConnectionString))
82+
{
83+
connection.Open();
84+
85+
if (reopenConnection)
86+
{
87+
connection.Close();
88+
connection.Open();
89+
}
90+
91+
var collation = connection.Query<string>(@"select @@collation_connection;").Single();
92+
var expected = connection.ServerVersion.StartsWith("8.0") ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci";
93+
Assert.Equal(expected, collation);
94+
}
95+
}
96+
3497
readonly DatabaseFixture m_database;
3598
}
3699
}

0 commit comments

Comments
 (0)