10000 Read GUIDs from out parameters. Fixes #1528 (#1546) · mysql-net/MySqlConnector@97b776b · GitHub
[go: up one dir, main page]

Skip to content

Commit 97b776b

Browse files
authored
Read GUIDs from out parameters. Fixes #1528 (#1546)
1 parent 5c0df94 commit 97b776b

File tree

10 files changed

+206
-69
lines changed

10 files changed

+206
-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
@@ -39,11 +39,11 @@ internal sealed class CachedProcedure
3939
object o => Encoding.UTF8.GetString((byte[]) o),
4040
};
4141

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

4949
return new CachedProcedure(schema, component, parsedParameters);
@@ -91,7 +91,8 @@ FROM information_schema.parameters
9191
!reader.IsDBNull(2) ? reader.GetString(2) : "",
9292
dataType,
9393
unsigned,
94-
length
94+
length,
95+
connection.GuidFormat
9596
));
9697
}
9798
}
@@ -132,14 +133,18 @@ internal MySqlParameterCollection AlignParamsWithDb(MySqlParameterCollection? pa
132133
if (!alignParam.HasSetDbType)
133134
alignParam.MySqlDbType = cachedParam.MySqlDbType;
134135

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

139144
return alignedParams;
140145
}
141146

142-
internal static List<CachedParameter> ParseParameters(string parametersSql)
147+
internal static List<CachedParameter> ParseParameters(string parametersSql, MySqlGuidFormat guidFormat)
143148
{
144149
// strip comments
145150
parametersSql = s_cStyleComments.Replace(parametersSql, "");
@@ -184,7 +189,7 @@ internal static List<CachedParameter> ParseParameters(string parametersSql)
184189
var name = parts.Groups[1].Success ? parts.Groups[1].Value.Replace("``", "`") : parts.Groups[2].Value;
185190

186191
var dataType = ParseDataType(parts.Groups[3].Value, out var unsigned, out var length);
187-
cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, originalString));
192+
cachedParameters.Add(CreateCachedParameter(i + 1, direction, name, dataType, unsigned, length, guidFormat, originalString));
188193
}
189194

190195
return cachedParameters;
@@ -222,11 +227,11 @@ internal static string ParseDataType(string sql, out bool unsigned, out int leng
222227
return type ?? list[0];
223228
}
224229

225-
private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, string originalSql)
230+
private static CachedParameter CreateCachedParameter(int ordinal, string? direction, string name, string dataType, bool unsigned, int length, MySqlGuidFormat guidFormat, string originalSql)
226231
{
227232
try
228233
{
229-
return new CachedParameter(ordinal, direction, name, dataType, unsigned, length);
234+
return new CachedParameter(ordinal, direction, name, dataType, unsigned, length, guidFormat);
230235
}
231236
catch (NullReferenceException ex)
232237
{
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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,60 @@ public void SprocNameSpecialCharacters(string sprocName)
874874
}
875875
}
876876

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

tests/IntegrationTests/packages.lock.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,15 @@
429429
"Microsoft.CodeCoverage": "17.12.0"
430430
}
431431
},
432+
"Microsoft.NETFramework.ReferenceAssemblies": {
433+
"type": "Direct",
434+
"requested": "[1.0.3, )",
435+
"resolved": "1.0.3",
436+
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
437+
"dependencies": {
438+
"Microsoft.NETFramework.ReferenceAssemblies.net481": "1.0.3"
439+
}
440+
},
432441
"Microsoft.SourceLink.GitHub": {
433442
"type": "Direct",
434443
"requested": "[8.0.0, )",
@@ -570,6 +579,11 @@
570579
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
571580
}
572581
},
582+
"Microsoft.NETFramework.ReferenceAssemblies.net481": {
583+
"type": "Transitive",
584+
"resolved": "1.0.3",
585+
"contentHash": "Vv/20vgHS7VglVOVh8J3Iz/MA+VYKVRp9f7r2qiKBMuzviTOmocG70yq0Q8T5OTmCONkEAIJwETD1zhEfLkAXQ=="
586+
},
573587
"Microsoft.SourceLink.Common": {
574588
"type": "Transitive",
575589
"resolved": "8.0.0",

0 commit comments

Comments
 (0)
0