Skip to content

Commit 2d44a00

Browse files
committed
Use underlying enum type when writing binary parameters. Fixes #1421
1 parent 8748a72 commit 2d44a00

File tree

2 files changed

+109
-42
lines changed

2 files changed

+109
-42
lines changed

src/MySqlConnector/MySqlParameter.cs

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -637,116 +637,124 @@ internal void AppendBinary(ByteBufferWriter writer, StatementPreparerOptions opt
637637
{
638638
// stored in "null bitmap" only
639639
}
640-
else if (Value is string stringValue)
640+
else
641+
{
642+
AppendBinary(writer, Value, options);
643+
}
644+
}
645+
646+
private void AppendBinary(ByteBufferWriter writer, object value, StatementPreparerOptions options)
647+
{
648+
if (value is string stringValue)
641649
{
642650
writer.WriteLengthEncodedString(stringValue);
643651
}
644-
else if (Value is char charValue)
652+
else if (value is char charValue)
645653
{
646654
writer.WriteLengthEncodedString(charValue.ToString());
647655
}
648-
else if (Value is sbyte sbyteValue)
656+
else if (value is sbyte sbyteValue)
649657
{
650658
writer.Write(unchecked((byte) sbyteValue));
651659
}
652-
else if (Value is byte byteValue)
660+
else if (value is byte byteValue)
653661
{
654662
writer.Write(byteValue);
655663
}
656-
else if (Value is bool boolValue)
664+
else if (value is bool boolValue)
657665
{
658666
writer.Write((byte) (boolValue ? 1 : 0));
659667
}
660-
else if (Value is short shortValue)
668+
else if (value is short shortValue)
661669
{
662670
writer.Write(unchecked((ushort) shortValue));
663671
}
664-
else if (Value is ushort ushortValue)
672+
else if (value is ushort ushortValue)
665673
{
666674
writer.Write(ushortValue);
667675
}
668-
else if (Value is int intValue)
676+
else if (value is int intValue)
669677
{
670678
writer.Write(intValue);
671679
}
672-
else if (Value is uint uintValue)
680+
else if (value is uint uintValue)
673681
{
674682
writer.Write(uintValue);
675683
}
676-
else if (Value is long longValue)
684+
else if (value is long longValue)
677685
{
678686
writer.Write(unchecked((ulong) longValue));
679687
}
680-
else if (Value is ulong ulongValue)
688+
else if (value is ulong ulongValue)
681689
{
682690
writer.Write(ulongValue);
683691
}
684-
else if (Value is byte[] byteArrayValue)
692+
else if (value is byte[] byteArrayValue)
685693
{
686694
writer.WriteLengthEncodedInteger(unchecked((ulong) byteArrayValue.Length));
687695
writer.Write(byteArrayValue);
688696
}
689-
else if (Value is ReadOnlyMemory<byte> readOnlyMemoryValue)
697+
else if (value is ReadOnlyMemory<byte> readOnlyMemoryValue)
690698
{
691699
writer.WriteLengthEncodedInteger(unchecked((ulong) readOnlyMemoryValue.Length));
692700
writer.Write(readOnlyMemoryValue.Span);
693701
}
694-
else if (Value is Memory<byte> memoryValue)
702+
else if (value is Memory<byte> memoryValue)
695703
{
696704
writer.WriteLengthEncodedInteger(unchecked((ulong) memoryValue.Length));
697705
writer.Write(memoryValue.Span);
698706
}
699-
else if (Value is ArraySegment<byte> arraySegmentValue)
707+
else if (value is ArraySegment<byte> arraySegmentValue)
700708
{
701709
writer.WriteLengthEncodedInteger(unchecked((ulong) arraySegmentValue.Count));
702710
writer.Write(arraySegmentValue);
703711
}
704-
else if (Value is MySqlGeometry geometry)
712+
else if (value is MySqlGeometry geometry)
705713
{
706714
writer.WriteLengthEncodedInteger(unchecked((ulong) geometry.ValueSpan.Length));
707715
writer.Write(geometry.ValueSpan);
708716
}
709-
else if (Value is MemoryStream memoryStream)
717+
else if (value is MemoryStream memoryStream)
710718
{
711719
if (!memoryStream.TryGetBuffer(out var streamBuffer))
712720
streamBuffer = new ArraySegment<byte>(memoryStream.ToArray());
713721
writer.WriteLengthEncodedInteger(unchecked((ulong) streamBuffer.Count));
714722
writer.Write(streamBuffer);
715723
}
716-
else if (Value is float floatValue)
724+
else if (value is float floatValue)
717725
{
718726
writer.Write(BitConverter.GetBytes(floatValue));
719727
}
720-
else if (Value is double doubleValue)
728+
else if (value is double doubleValue)
721729
{
722730
writer.Write(unchecked((ulong) BitConverter.DoubleToInt64Bits(doubleValue)));
723731
}
724-
else if (Value is decimal decimalValue)
732+
else if (value is decimal decimalValue)
725733
{
726734
writer.WriteLengthEncodedAsciiString(decimalValue.ToString(CultureInfo.InvariantCulture));
727735
}
728-
else if (Value is BigInteger bigInteger)
736+
else if (value is BigInteger bigInteger)
729737
{
730738
writer.WriteLengthEncodedAsciiString(bigInteger.ToString(CultureInfo.InvariantCulture));
731739
}
732-
else if (Value is MySqlDateTime mySqlDateTimeValue)
740+
else if (value is MySqlDateTime mySqlDateTimeValue)
733741
{
734742
if (mySqlDateTimeValue.IsValidDateTime)
735743
WriteDateTime(writer, mySqlDateTimeValue.GetDateTime());
736744
else
737745
writer.Write((byte) 0);
738746
}
739-
else if (Value is MySqlDecimal mySqlDecimal)
747+
else if (value is MySqlDecimal mySqlDecimal)
740748
{
741749
writer.WriteLengthEncodedAsciiString(mySqlDecimal.ToString());
742750
}
743751
#if NET6_0_OR_GREATER
744-
else if (Value is DateOnly dateOnlyValue)
752+
else if (value is DateOnly dateOnlyValue)
745753
{
746754
WriteDateOnly(writer, dateOnlyValue);
747755
}
748756
#endif
749-
else if (Value is DateTime dateTimeValue)
757+
else if (value is DateTime dateTimeValue)
750758
{
751759
if ((options & StatementPreparerOptions.DateTimeUtc) != 0 && dateTimeValue.Kind == DateTimeKind.Local)
752760
throw new MySqlException($"DateTime.Kind must not be Local when DateTimeKind setting is Utc (parameter name: {ParameterName})");
@@ -755,22 +763,22 @@ internal void AppendBinary(ByteBufferWriter writer, StatementPreparerOptions opt
755763

756764
WriteDateTime(writer, dateTimeValue);
757765
}
758-
else if (Value is DateTimeOffset dateTimeOffsetValue)
766+
else if (value is DateTimeOffset dateTimeOffsetValue)
759767
{
760768
// store as UTC as it will be read as such when deserialized from a timespan column
761769
WriteDateTime(writer, dateTimeOffsetValue.UtcDateTime);
762770
}
763771
#if NET6_0_OR_GREATER
764-
else if (Value is TimeOnly timeOnlyValue)
772+
else if (value is TimeOnly timeOnlyValue)
765773
{
766774
WriteTime(writer, timeOnlyValue.ToTimeSpan());
767775
}
768776
#endif
769-
else if (Value is TimeSpan ts)
777+
else if (value is TimeSpan ts)
770778
{
771779
WriteTime(writer, ts);
772780
}
773-
else if (Value is Guid guidValue)
781+
else if (value is Guid guidValue)
774782
{
775783
StatementPreparerOptions guidOptions = options & StatementPreparerOptions.GuidFormatMask;
776784
if (guidOptions is StatementPreparerOptions.GuidFormatBinary16 or StatementPreparerOptions.GuidFormatTimeSwapBinary16 or StatementPreparerOptions.GuidFormatLittleEndianBinary16)
@@ -817,53 +825,55 @@ internal void AppendBinary(ByteBufferWriter writer, StatementPreparerOptions opt
817825
writer.Advance(guidLength);
818826
}
819827
}
820-
else if (Value is ReadOnlyMemory<char> readOnlyMemoryChar)
828+
else if (value is ReadOnlyMemory<char> readOnlyMemoryChar)
821829
{
822830
writer.WriteLengthEncodedString(readOnlyMemoryChar.Span);
823831
}
824-
else if (Value is Memory<char> memoryChar)
832+
else if (value is Memory<char> memoryChar)
825833
{
826834
writer.WriteLengthEncodedString(memoryChar.Span);
827835
}
828-
else if (Value is StringBuilder stringBuilder)
836+
else if (value is StringBuilder stringBuilder)
829837
{
830838
writer.WriteLengthEncodedString(stringBuilder);
831839
}
832-
else if ((MySqlDbType is MySqlDbType.String or MySqlDbType.VarChar) && HasSetDbType && Value is Enum stringEnumValue)
840+
else if ((MySqlDbType is MySqlDbType.String or MySqlDbType.VarChar) && HasSetDbType && value is Enum stringEnumValue)
833841
{
834842
writer.WriteLengthEncodedString(stringEnumValue.ToString("G"));
835843
}
836-
else if (Value is Enum)
844+
else if (value is Enum)
837845
{
838-
writer.Write(Convert.ToInt32(Value, CultureInfo.InvariantCulture));
846+
// using the underlying type matches the log in TypeMapper.GetDbTypeMapping, which controls the column type value that was sent to the server
847+
var underlyingValue = Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture);
848+
AppendBinary(writer, underlyingValue, options);
839849
}
840850
else if (MySqlDbType == MySqlDbType.Int16)
841851
{
842-
writer.Write((ushort) (short) Value);
852+
writer.Write((ushort) (short) value);
843853
}
844854
else if (MySqlDbType == MySqlDbType.UInt16)
845855
{
846-
writer.Write((ushort) Value);
856+
writer.Write((ushort) value);
847857
}
848858
else if (MySqlDbType == MySqlDbType.Int32)
849859
{
850-
writer.Write((int) Value);
860+
writer.Write((int) value);
851861
}
852862
else if (MySqlDbType == MySqlDbType.UInt32)
853863
{
854-
writer.Write((uint) Value);
864+
writer.Write((uint) value);
855865
}
856866
else if (MySqlDbType == MySqlDbType.Int64)
857867
{
858-
writer.Write((ulong) (long) Value);
868+
writer.Write((ulong) (long) value);
859869
}
860870
else if (MySqlDbType == MySqlDbType.UInt64)
861871
{
862-
writer.Write((ulong) Value);
872+
writer.Write((ulong) value);
863873
}
864874
else
865875
{
866-
throw new NotSupportedException($"Parameter type {Value.GetType().Name} is not supported; see https://fl.vu/mysql-param-type. Value: {Value}");
876+
throw new NotSupportedException($"Parameter type {value.GetType().Name} is not supported; see https://fl.vu/mysql-param-type. Value: {value}");
867877
}
868878
}
869879

tests/IntegrationTests/PreparedCommandTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,63 @@ public static IEnumerable<object[]> GetDifferentTypeInsertAndQueryData()
433433
}
434434
}
435435

436+
[Theory, MemberData(nameof(GetDifferentWidthData))]
437+
public void InsertDifferentWidthParameters(string dataType1, object value1, string dataType2, object value2)
438+
{
439+
using var connection = CreateConnection();
440+
using var command = connection.CreateCommand();
441+
command.CommandText = $"""
442+
drop table if exists parameter_width;
443+
create table parameter_width(value1 {dataType1} not null, value2 {dataType2} not null);
444+
""";
445+
command.ExecuteNonQuery();
446+
447+
command.CommandText = "insert into parameter_width(value1, value2) values(@value1, @value2);";
448+
command.Parameters.AddWithValue("@value1", value1);
449+
command.Parameters.AddWithValue("@value2", value2);
450+
command.Prepare();
451+
command.ExecuteNonQuery();
452+
453+
using var queryCommand = connection.CreateCommand();
454+
queryCommand.CommandText = "select value1, value2 from parameter_width;";
455+
using var reader = queryCommand.ExecuteReader();
456+
Assert.True(reader.Read());
457+
Assert.Equal(Convert.ToInt32(value1), reader.GetInt32(0));
458+
Assert.Equal(Convert.ToInt32(value2), reader.GetInt32(1));
459+
Assert.False(reader.Read());
460+
}
461+
462+
public static IEnumerable<object[]> GetDifferentWidthData()
463+
{
464+
var dataTypes = new string[] { "TINYINT", "SMALLINT", "INT" };
465+
var values = new object[] { (sbyte) 100, (short) 110, 120, OneByteEnum.Value, TwoByteEnum.Value };
466+
467+
foreach (var dataType1 in dataTypes)
468+
{
469+
foreach (var value1 in values)
470+
{
471+
foreach (var dataType2 in dataTypes)
472+
{
473+
foreach (var value2 in values)
474+
{
475+
yield return new object[] { dataType1, value1, dataType2, value2 };
476+
}
477+
}
478+
}
479+
}
480+
}
481+
482+
private enum OneByteEnum : sbyte
483+
{
484+
Value = 101,
485+
}
486+
487+
private enum TwoByteEnum : short
488+
{
489+
Value = 111,
490+
}
491+
492+
436493
private static MySqlConnection CreateConnection()
437494
{
438495
var connection = new MySqlConnection(AppConfig.ConnectionString);

0 commit comments

Comments
 (0)