Skip to content

Commit 38f9bfe

Browse files
committed
Read GUIDs from out parameters. Fixes #1528
Signed-off-by: Bradley Grainger <[email protected]>
1 parent fc59a06 commit 38f9bfe

File tree

8 files changed

+171
-69
lines changed

8 files changed

+171
-69
lines changed

src/MySqlConnector/ColumnReaders/ColumnReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ public static ColumnReader Create(bool isBinary, ColumnDefinitionPayload columnD
4545
return BitColumnReader.Instance;
4646

4747
case ColumnType.String:
48+
case ColumnType.VarString:
4849
if (connection.GuidFormat == MySqlGuidFormat.Char36 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 36)
4950
return GuidChar36ColumnReader.Instance;
5051
if (connection.GuidFormat == MySqlGuidFormat.Char32 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 32)
5152
return GuidChar32ColumnReader.Instance;
52-
goto case ColumnType.VarString;
53+
goto case ColumnType.VarChar;
5354

54-
case ColumnType.VarString:
5555
case ColumnType.VarChar:
5656
case ColumnType.TinyBlob:
5757
case ColumnType.Blob:

src/MySqlConnector/Core/CachedParameter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace MySqlConnector.Core;
22

33
internal sealed class CachedParameter
44
{
5-
public CachedParameter(int ordinalPosition, string? mode, string name, string dataType, bool unsigned, int length)
5+
public CachedParameter(int ordinalPosition, string? mode, string name, string dataType, bool unsigned, int length, MySqlGuidFormat guidFormat)
66
{
77
Position = ordinalPosition;
88
if (Position == 0)
@@ -14,7 +14,7 @@ public CachedParameter(int ordinalPosition, string? mode, string name, string da
1414
else if (string.Equals(mode, "out", StringComparison.OrdinalIgnoreCase))
1515
Direction = ParameterDirection.Output;
1616
Name = name;
17-
MySqlDbType = TypeMapper.Instance.GetMySqlDbType(dataType, unsigned, length);
17+
MySqlDbType = TypeMapper.Instance.GetMySqlDbType(dataType, unsigned, length, guidFormat);
1818
Length = length;
1919
}
2020

src/MySqlConnector/Core/CachedProcedure.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ internal sealed class CachedProcedure
4040
object o => Encoding.UTF8.GetString((byte[]) o),
4141
};
4242

43-
var parsedParameters = ParseParameters(parametersSql);
43+
var parsedParameters = ParseParameters(parametersSql, connection.GuidFormat);
4444
if (returnsSql.Length != 0)
4545
{
4646
var returnDataType = ParseDataType(returnsSql, out var unsigned, out var length);
47-
parsedParameters.Insert(0, CreateCachedParameter(0, null, "", returnDataType, unsigned, length, returnsSql));
47+
parsedParameters.Insert(0, CreateCachedParameter(0, null, "", returnDataType, unsigned, length, connection.GuidFormat, returnsSql));
4848
}
4949

5050
return new CachedProcedure(schema, component, parsedParameters);
@@ -92,7 +92,8 @@ FROM information_schema.parameters
9292
!reader.IsDBNull(2) ? reader.GetString(2) : "",
9393
dataType,
9494
unsigned,
95-
length
95+
length,
96+
connection.GuidFormat
9697
));
9798
}
9899
}
@@ -133,14 +134,18 @@ internal MySqlParameterCollection AlignParamsWithDb(MySqlParameterCollection? pa
133134
if (!alignParam.HasSetDbType)
134135
alignParam.MySqlDbType = cachedParam.MySqlDbType;
135136

137+
// for a GUID column, pass along the length so the out parameter can be cast to the right size
138+
if (alignParam.MySqlDbType == MySqlDbType.Guid && cachedParam.Direction is ParameterDirection.Output or ParameterDirection.InputOutput)
139+
alignParam.Size = cachedParam.Length;
140+
136141
// cached parameters are ordered by ordinal position
137142
alignedParams.Add(alignParam);
138143
}
139144

140145
return alignedParams;
141146
}
142147

143-
internal static List<CachedParameter> ParseParameters(string parametersSql)
148+
internal static List<CachedParameter> ParseParameters(string parametersSql, MySqlGuidFormat guidFormat)
144149
{
145150
// strip comments
146151
parametersSql = s_cStyleComments.Replace(parametersSql, "");
@@ -185,7 +190,7 @@ internal static List<CachedParameter> ParseParameters(string parametersSql)
185190
var name = parts.Groups[1].Success ? parts.Groups[1].Value.Replace("``", "`") : parts.Groups[2].Value;
186191

187192
var dataType = ParseDataType(parts.Groups[3].Value, out var unsigned, out var length);
188-
cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, originalString));
193+
cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, guidFormat, originalString));
189194
}
190195

191196
return cachedParameters;
@@ -223,11 +228,11 @@ internal static string ParseDataType(string sql, out bool unsigned, out int leng
223228
return type ?? list[0];
224229
}
225230

226-
private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, string originalSql)
231+
private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, MySqlGuidFormat guidFormat, string originalSql)
227232
{
228233
try
229234
{
230-
return new CachedParameter(ordinal, direction, name, dataType, unsigned, length);
235+
return new CachedParameter(ordinal, direction, name, dataType, unsigned, length, guidFormat);
231236
}
232237
catch (NullReferenceException ex)
233238
{
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1+
using System.Runtime.CompilerServices;
2+
13
namespace MySqlConnector.Core;
24

3-
internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTypeMapping, MySqlDbType mySqlDbType, bool isUnsigned = false, bool binary = false, int length = 0, string? simpleDataTypeName = null, string? createFormat = null, long columnSize = 0)
5+
internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTypeMapping, MySqlDbType mySqlDbType, bool isUnsigned = false, bool binary = false, int length = 0, string? simpleDataTypeName = null, string? createFormat = null, long columnSize = 0, MySqlGuidFormat guidFormat = MySqlGuidFormat.Default)
46
{
5-
public static string CreateLookupKey(string columnTypeName, bool isUnsigned, int length) => $"{columnTypeName}|{(isUnsigned ? "u" : "s")}|{length}";
7+
public static string CreateLookupKey(string columnTypeName, bool isUnsigned, int length, MySqlGuidFormat guidFormat) =>
8+
$"{columnTypeName}|{(isUnsigned ? "u" : "s")}|{length}|{GetGuidFormatLookupKey(guidFormat)}";
9+
10+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
11+
private static string GetGuidFormatLookupKey(MySqlGuidFormat guidFormat) =>
12+
guidFormat switch
13+
{
14+
MySqlGuidFormat.Char36 => "c36",
15+
MySqlGuidFormat.Char32 => "c32",
16+
MySqlGuidFormat.Binary16 or MySqlGuidFormat.TimeSwapBinary16 or MySqlGuidFormat.LittleEndianBinary16 => "b16",
17+
_ => "def",
18+
};
619

720
public string DataTypeName { get; } = dataTypeName;
821
public string SimpleDataTypeName { get; } = simpleDataTypeName ?? dataTypeName;
@@ -13,6 +26,7 @@ internal sealed class ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTy
1326
public long ColumnSize { get; } = columnSize;
1427
public bool IsUnsigned { get; } = isUnsigned;
1528
public int Length { get; } = length;
29+
public MySqlGuidFormat GuidFormat { get; } = guidFormat;
1630

17-
public string CreateLookupKey() => CreateLookupKey(DataTypeName, IsUnsigned, Length);
31+
public string CreateLookupKey() => CreateLookupKey(DataTypeName, IsUnsigned, Length, GuidFormat);
1832
}

src/MySqlConnector/Core/SingleCommandPayloadCreator.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,24 @@ private static bool WriteStoredProcedure(IMySqlCommand command, IDictionary<stri
214214
break;
215215
case ParameterDirection.Output:
216216
outParameters.Add(param);
217-
outParameterNames.Add(outName);
218217
argParameterNames.Add(outName);
218+
219+
// special handling for GUIDs to ensure that the result set has a type and length that will be autodetected as a GUID
220+
switch (param.MySqlDbType, param.Size)
221+
{
222+
case (MySqlDbType.Guid, 16):
223+
outParameterNames.Add($"CAST({outName} AS BINARY(16))");
224+
break;
225+
case (MySqlDbType.Guid, 32):
226+
outParameterNames.Add($"CAST({outName} AS CHAR(32))");
227+
break;
228+
case (MySqlDbType.Guid, 36):
229+
outParameterNames.Add($"CAST({outName} AS CHAR(36))");
230+
break;
231+
default:
232+
outParameterNames.Add(outName);
233+
break;
234+
}
219235
break;
220236
case ParameterDirection.ReturnValue:
221237
returnParameter = param;

src/MySqlConnector/Core/TypeMapper.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ private TypeMapper()
115115
#endif
116116
var typeGuid = AddDbTypeMapping(new(typeof(Guid), [DbType.Guid], convert: convertGuid));
117117
AddColumnTypeMetadata(new("CHAR", typeGuid, MySqlDbType.Guid, length: 36, simpleDataTypeName: "CHAR(36)", createFormat: "CHAR(36)"));
118+
AddColumnTypeMetadata(new("CHAR", typeGuid, MySqlDbType.Guid, length: 32, guidFormat: MySqlGuidFormat.Char32));
119+
AddColumnTypeMetadata(new("CHAR", typeGuid, MySqlDbType.Guid, length: 36, guidFormat: MySqlGuidFormat.Char36));
120+
AddColumnTypeMetadata(new("BINARY", typeGuid, MySqlDbType.Guid, binary: true, length: 16, guidFormat: MySqlGuidFormat.Binary16));
118121

119122
// null
120123
var typeNull = AddDbTypeMapping(new(typeof(object), [DbType.Object]));
@@ -181,15 +184,20 @@ private void AddColumnTypeMetadata(ColumnTypeMetadata columnTypeMetadata)
181184

182185
public DbTypeMapping? GetDbTypeMapping(string columnTypeName, bool unsigned = false, int length = 0)
183186
{
184-
return GetColumnTypeMetadata(columnTypeName, unsigned, length)?.DbTypeMapping;
187+
return GetColumnTypeMetadata(columnTypeName, unsigned, length, MySqlGuidFormat.Default)?.DbTypeMapping;
185188
}
186189

187-
public MySqlDbType GetMySqlDbType(string typeName, bool unsigned, int length) => GetColumnTypeMetadata(typeName, unsigned, length)!.MySqlDbType;
190+
public MySqlDbType GetMySqlDbType(string typeName, bool unsigned, int length, MySqlGuidFormat guidFormat) =>
191+
GetColumnTypeMetadata(typeName, unsigned, length, guidFormat)!.MySqlDbType;
188192

189-
private ColumnTypeMetadata? GetColumnTypeMetadata(string columnTypeName, bool unsigned, int length)
193+
private ColumnTypeMetadata? GetColumnTypeMetadata(string columnTypeName, bool unsigned, int length, MySqlGuidFormat guidFormat)
190194
{
191-
if (!m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length), out var columnTypeMetadata) && length != 0)
192-
m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, 0), out columnTypeMetadata);
195+
if (m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length, guidFormat), out var columnTypeMetadata))
196+
return columnTypeMetadata;
197+
if (guidFormat != MySqlGuidFormat.Default && m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, length, MySqlGuidFormat.Default), out columnTypeMetadata))
198+
return columnTypeMetadata;
199+
if (length != 0)
200+
m_columnTypeMetadataLookup.TryGetValue(ColumnTypeMetadata.CreateLookupKey(columnTypeName, unsigned, 0, MySqlGuidFormat.Default), out columnTypeMetadata);
193201
return columnTypeMetadata;
194202
}
195203

tests/IntegrationTests/StoredProcedureTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,53 @@ public void SprocNameSpecialCharacters(string sprocName)
874874
}
875875
}
876876

877+
[Theory]
878+
[InlineData(MySqlGuidFormat.Binary16, "BINARY(16)", "X'BABD8384C908499C9D95C02ADA94A970'", null)]
879+
[InlineData(MySqlGuidFormat.Binary16, "BINARY(16)", "X'BABD8384C908499C9D95C02ADA94A970'", MySqlDbType.Binary)]
880+
[InlineData(MySqlGuidFormat.Binary16, "BINARY(16)", "X'BABD8384C908499C9D95C02ADA94A970'", MySqlDbType.Guid)]
881+
[InlineData(MySqlGuidFormat.Char32, "CHAR(32)", "'BABD8384C908499C9D95C02ADA94A970'", null)]
882+
[InlineData(MySqlGuidFormat.Char32, "CHAR(32)", "'BABD8384C908499C9D95C02ADA94A970'", MySqlDbType.Guid)]
883+
[InlineData(MySqlGuidFormat.Char32, "CHAR(32)", "'BABD8384C908499C9D95C02ADA94A970'", MySqlDbType.String)]
884+
[InlineData(MySqlGuidFormat.Char36, "CHAR(36)", "'BABD8384-C908-499C-9D95-C02ADA94A970'", null)]
885+
[InlineData(MySqlGuidFormat.Char36, "CHAR(36)", "'BABD8384-C908-499C-9D95-C02ADA94A970'", MySqlDbType.Guid)]
886+
[InlineData(MySqlGuidFormat.Char36, "CHAR(36)", "'BABD8384-C908-499C-9D95-C02ADA94A970'", MySqlDbType.VarChar)]
887+
public void StoredProcedureReturnsGuid(MySqlGuidFormat guidFormat, string columnDefinition, string columnValue, MySqlDbType? mySqlDbType)
888+
{
889+
var csb = AppConfig.CreateConnectionStringBuilder();
890+
csb.GuidFormat = guidFormat;
891+
csb.Pooling = false;
892+
using var connection = new MySqlConnection(csb.ConnectionString);
893+
connection.Open();
894+
895+
using (var command = new MySqlCommand($"""
896+
DROP TABLE IF EXISTS out_guid_table;
897+
CREATE TABLE out_guid_table (id INT PRIMARY KEY AUTO_INCREMENT, guid {columnDefinition});
898+
INSERT INTO out_guid_table (guid) VALUES ({columnValue});
899+
DROP PROCEDURE IF EXISTS out_guid;
900+
CREATE PROCEDURE out_guid
901+
(
902+
OUT out_name {columnDefinition}
903+
)
904+
BEGIN
905+
SELECT guid INTO out_name FROM out_guid_table;
906+
END;
907+
""", connection))
908+
{
909+
command.ExecuteNonQuery();
910+
}
911+
912+
using (var command = new MySqlCommand("out_guid", connection))
913+
{
914+
command.CommandType = CommandType.StoredProcedure;
915+
var param = new MySqlParameter("out_name", null) { Direction = ParameterDirection.Output };
916+
if (mySqlDbType.HasValue && DateTime.UtcNow.Year == 2024)
917+
param.MySqlDbType = mySqlDbType.Value;
918+
command.Parameters.Add(param);
919+
command.ExecuteNonQuery();
920+
Assert.Equal(new Guid("BABD8384C908499C9D95C02ADA94A970"), param.Value);
921+
}
922+
}
923+
877924
private static string NormalizeSpaces(string input)
878925
{
879926
input = input.Replace('\r', ' ');

0 commit comments

Comments
 (0)