Skip to content

Commit 9222188

Browse files
committed
Work around server bug reading BIT column. Fixes #713
1 parent 2afa3d1 commit 9222188

File tree

4 files changed

+65
-4
lines changed

4 files changed

+65
-4
lines changed

src/MySqlConnector/Core/BinaryRow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected override object GetValueCore(ReadOnlySpan<byte> data, ColumnDefinition
8787
return isUnsigned ? (object) MemoryMarshal.Read<ulong>(data) : MemoryMarshal.Read<long>(data);
8888

8989
case ColumnType.Bit:
90-
return ReadBit(data, columnDefinition.ColumnFlags);
90+
return ReadBit(data, columnDefinition);
9191

9292
case ColumnType.String:
9393
if (Connection.GuidFormat == MySqlGuidFormat.Char36 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 36)

src/MySqlConnector/Core/Row.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,16 +433,22 @@ protected static Guid CreateGuidFromBytes(MySqlGuidFormat guidFormat, ReadOnlySp
433433
#endif
434434
}
435435

436-
protected static object ReadBit(ReadOnlySpan<byte> data, ColumnFlags columnFlags)
436+
protected static object ReadBit(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
437437
{
438-
if ((columnFlags & ColumnFlags.Binary) == 0)
438+
if ((columnDefinition.ColumnFlags & ColumnFlags.Binary) == 0)
439439
{
440440
// when the Binary flag IS NOT set, the BIT column is transmitted as MSB byte array
441441
ulong bitValue = 0;
442442
for (int i = 0; i < data.Length; i++)
443443
bitValue = bitValue * 256 + data[i];
444444
return bitValue;
445445
}
446+
else if (columnDefinition.ColumnLength <= 5 && data.Length == 1 && data[0] < (byte) (1 << (int) columnDefinition.ColumnLength))
447+
{
448+
// a server bug may return the data as binary even when we expect text: https://github.com/mysql-net/MySqlConnector/issues/713
449+
// in this case, the data can't possibly be an ASCII digit, so assume it's the binary serialisation of BIT(n) where n <= 5
450+
return (ulong) data[0];
451+
}
446452
else
447453
{
448454
// when the Binary flag IS set, the BIT column is transmitted as text

src/MySqlConnector/Core/TextRow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected override object GetValueCore(ReadOnlySpan<byte> data, ColumnDefinition
5050
return isUnsigned ? (object) ParseUInt64(data) : ParseInt64(data);
5151

5252
case ColumnType.Bit:
53-
return ReadBit(data, columnDefinition.ColumnFlags);
53+
return ReadBit(data, columnDefinition);
5454

5555
case ColumnType.String:
5656
if (Connection.GuidFormat == MySqlGuidFormat.Char36 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 36)

tests/SideBySide/QueryTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,61 @@ public void NoBackslashEscapes()
10931093
}
10941094
#endif
10951095

1096+
[SkippableFact(Baseline = "https://bugs.mysql.com/bug.php?id=97067")]
1097+
public async Task QueryBit()
1098+
{
1099+
using var connection = new MySqlConnection(AppConfig.ConnectionString);
1100+
await connection.OpenAsync();
1101+
await connection.ExecuteAsync(@"
1102+
DROP TABLE IF EXISTS query_bit;
1103+
CREATE TABLE query_bit(name TEXT, value BIT(1));
1104+
INSERT INTO query_bit VALUES('a', 1);
1105+
");
1106+
1107+
using var command = new MySqlCommand(@"
1108+
SELECT value FROM query_bit;
1109+
1110+
SELECT MAX(value) FROM query_bit;
1111+
1112+
SELECT COALESCE(value, 0) FROM query_bit;
1113+
1114+
SELECT CASE
1115+
WHEN name IS NOT NULL THEN value
1116+
ELSE NULL
1117+
END AS value
1118+
FROM query_bit;", connection);
1119+
using var reader = await command.ExecuteReaderAsync();
1120+
1121+
Assert.True(await reader.ReadAsync());
1122+
Assert.Equal(1ul, reader.GetValue(0));
1123+
Assert.False(await reader.ReadAsync());
1124+
1125+
Assert.True(await reader.NextResultAsync());
1126+
1127+
Assert.True(await reader.ReadAsync());
1128+
Assert.Equal(1ul, reader.GetValue(0));
1129+
Assert.False(await reader.ReadAsync());
1130+
1131+
Assert.True(await reader.NextResultAsync());
1132+
1133+
Assert.True(await reader.ReadAsync());
1134+
var value = reader.GetValue(0);
1135+
if (value is byte[] byteArray)
1136+
Assert.Equal(new byte[] { 49 }, byteArray); // server bug returns '1' as a blob
1137+
else
1138+
Assert.Equal(1m, value);
1139+
Assert.False(await reader.ReadAsync());
1140+
1141+
Assert.True(await reader.NextResultAsync());
1142+
1143+
Assert.True(await reader.ReadAsync());
1144+
// MySQL returns ulong, MariaDB returns decimal; GetBoolean will coerce both
1145+
Assert.True(reader.GetBoolean(0));
1146+
Assert.False(await reader.ReadAsync());
1147+
1148+
Assert.False(await reader.NextResultAsync());
1149+
}
1150+
10961151
class BoolTest
10971152
{
10981153
public int Id { get; set; }

0 commit comments

Comments
 (0)