Skip to content

Commit

Permalink
Use server default collation. Fixes #626
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrainger committed Apr 11, 2019
1 parent d38be8b commit eedf270
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 8 deletions.
23 changes: 16 additions & 7 deletions src/MySqlConnector/Core/ServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
m_supportsSessionTrack = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.SessionTrack) != 0;
var serverSupportsSsl = (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Ssl) != 0;
m_characterSet = ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? CharacterSet.Utf8Mb4GeneralCaseInsensitive : CharacterSet.Utf8GeneralCaseInsensitive;
m_setNamesPayload = ServerVersion.Version >= ServerVersions.SupportsUtf8Mb4 ? s_setNamesUtf8mb4Payload : s_setNamesUtf8Payload;

Log.Info("Session{0} made connection; ServerVersion={1}; ConnectionId={2}; Compression={3}; Attributes={4}; DeprecateEof={5}; Ssl={6}; SessionTrack={7}",
m_logArguments[0], ServerVersion.OriginalString, ConnectionId,
Expand Down Expand Up @@ -370,6 +371,11 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
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.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);

if (ShouldGetRealServerDetails())
await GetRealServerDetailsAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
}
Expand All @@ -394,16 +400,12 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
// clear all prepared statements; resetting the connection will clear them on the server
ClearPreparedStatements();

PayloadData payload;
if (DatabaseOverride is null && (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0 || ServerVersion.MariaDbVersion?.CompareTo(ServerVersions.MariaDbSupportsResetConnection) >= 0))
{
m_logArguments[1] = ServerVersion.OriginalString;
Log.Debug("Session{0} ServerVersion={1} supports reset connection; sending reset connection request", m_logArguments);
await SendAsync(ResetConnectionPayload.Instance, ioBehavior, cancellationToken).ConfigureAwait(false);
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);

// the "reset connection" packet also resets the connection charset, so we need to change that back to our default
await SendAsync(s_setNamesUtf8mb4Payload, ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
}
Expand All @@ -424,7 +426,7 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
using (var changeUserPayload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_characterSet, m_supportsConnectionAttributes ? cs.ConnectionAttributes : null))
await SendAsync(changeUserPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
{
Log.Debug("Session{0} optimistic reauthentication failed; logging in again", m_logArguments);
Expand All @@ -433,6 +435,11 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
OkPayload.Create(payload.AsSpan(), SupportsDeprecateEof, SupportsSessionTrack);
}

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

return true;
}
catch (IOException ex)
Expand Down Expand Up @@ -1393,7 +1400,8 @@ private enum State
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-----
static int s_lastId;
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(ServerSession));
static readonly PayloadData s_setNamesUtf8mb4Payload = QueryPayload.Create("SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;");
static readonly PayloadData s_setNamesUtf8Payload = QueryPayload.Create("SET NAMES utf8;");
static readonly PayloadData s_setNamesUtf8mb4Payload = QueryPayload.Create("SET NAMES utf8mb4;");

readonly object m_lock;
readonly object[] m_logArguments;
Expand All @@ -1412,6 +1420,7 @@ private enum State
bool m_supportsDeprecateEof;
bool m_supportsSessionTrack;
CharacterSet m_characterSet;
PayloadData m_setNamesPayload;
Dictionary<string, PreparedStatements> m_preparedStatements;
}
}
2 changes: 1 addition & 1 deletion tests/MySqlConnector.Tests/FakeMySqlServerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task RunAsync(TcpClient client, CancellationToken token)
case CommandKind.Query:
var query = Encoding.UTF8.GetString(bytes, 1, bytes.Length - 1);
Match match;
if (query == "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;")
if (query == "SET NAMES utf8mb4;")
{
await SendAsync(stream, 1, WriteOk);
}
Expand Down
63 changes: 63 additions & 0 deletions tests/SideBySide/CharacterSetTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Linq;
using Dapper;
using MySql.Data.MySqlClient;
#if !BASELINE
using MySqlConnector.Protocol;
using MySqlConnector.Protocol.Serialization;
Expand Down Expand Up @@ -31,6 +33,67 @@ public void MaxLength()
}
#endif

[Theory]
[InlineData(false)]
[InlineData(true)]
public void IllegalMixOfCollations(bool reopenConnection)
{
var csb = AppConfig.CreateConnectionStringBuilder();
csb.AllowUserVariables = true;
using (var connection = new MySqlConnection(csb.ConnectionString))
{
connection.Open();
connection.Execute(@"
DROP TABLE IF EXISTS mix_collations;
CREATE TABLE mix_collations (
id int(11) NOT NULL AUTO_INCREMENT,
test_col varchar(10) DEFAULT NULL,
PRIMARY KEY (id),
KEY ix_test (test_col)
);
INSERT INTO mix_collations (test_col)
VALUES ('a'), ('b'), ('c'), ('d'), ('e'), ('f'), ('g'), ('h'), ('i'), ('j');");

if (reopenConnection)
{
connection.Close();
connection.Open();
}

using (var reader = connection.ExecuteReader(@"
SET @param = 'B';
SELECT * FROM mix_collations a WHERE a.test_col = @param"))
{
Assert.True(reader.Read());
}
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void CollationConnection(bool reopenConnection)
{
var csb = AppConfig.CreateConnectionStringBuilder();
#if BASELINE
csb.CharacterSet = "utf8mb4";
#endif
using (var connection = new MySqlConnection(csb.ConnectionString))
{
connection.Open();

if (reopenConnection)
{
connection.Close();
connection.Open();
}

var collation = connection.Query<string>(@"select @@collation_connection;").Single();
var expected = connection.ServerVersion.StartsWith("8.0") ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci";
Assert.Equal(expected, collation);
}
}

readonly DatabaseFixture m_database;
}
}

0 comments on commit eedf270

Please sign in to comment.