Skip to content

Commit 699f2b6

Browse files
elemountbgrainger
authored andcommitted
Implement GetSchemaTable. Fixes #307
1 parent a2cc99d commit 699f2b6

File tree

5 files changed

+221
-22
lines changed

5 files changed

+221
-22
lines changed

src/MySqlConnector/MySqlClient/MySqlDataReader.cs

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Data;
55
using System.Data.Common;
6+
using System.Globalization;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using MySql.Data.MySqlClient.Results;
@@ -202,7 +203,15 @@ public override long GetChars(int ordinal, long dataOffset, char[] buffer, int b
202203
public override int VisibleFieldCount => FieldCount;
203204

204205
#if !NETSTANDARD1_3
205-
public override DataTable GetSchemaTable() => throw new NotSupportedException();
206+
public override DataTable GetSchemaTable()
207+
{
208+
if (m_schemaTable == null)
209+
{
210+
m_schemaTable = BuildSchemaTable();
211+
}
212+
213+
return m_schemaTable;
214+
}
206215

207216
public override void Close()
208217
{
@@ -270,6 +279,104 @@ internal async Task ReadFirstResultSetAsync(IOBehavior ioBehavior)
270279
m_resultSetBuffered = m_resultSet;
271280
}
272281

282+
#if !NETSTANDARD1_3
283+
internal DataTable BuildSchemaTable()
284+
{
285+
var colDefinitions = GetResultSet().ColumnDefinitions;
286+
DataTable schemaTable = new DataTable("SchemaTable");
287+
schemaTable.Locale = CultureInfo.InvariantCulture;
288+
schemaTable.MinimumCapacity = colDefinitions.Length;
289+
290+
var columnName = new DataColumn(SchemaTableColumn.ColumnName, typeof(string));
291+
var ordinal = new DataColumn(SchemaTableColumn.ColumnOrdinal, typeof(int));
292+
var size = new DataColumn(SchemaTableColumn.ColumnSize, typeof(int));
293+
var precision = new DataColumn(SchemaTableColumn.NumericPrecision, typeof(int));
294+
var scale = new DataColumn(SchemaTableColumn.NumericScale, typeof(int));
295+
var dataType = new DataColumn(SchemaTableColumn.DataType, typeof(System.Type));
296+
var providerType = new DataColumn(SchemaTableColumn.ProviderType, typeof(int));
297+
var isLong = new DataColumn(SchemaTableColumn.IsLong, typeof(bool));
298+
var allowDBNull = new DataColumn(SchemaTableColumn.AllowDBNull, typeof(bool));
299+
var isReadOnly = new DataColumn(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
300+
var isRowVersion = new DataColumn(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
301+
var isUnique = new DataColumn(SchemaTableColumn.IsUnique, typeof(bool));
302+
var isKey = new DataColumn(SchemaTableColumn.IsKey, typeof(bool));
303+
var isAutoIncrement = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
304+
var isHidden = new DataColumn(SchemaTableOptionalColumn.IsHidden, typeof(bool));
305+
var baseCatalogName = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
306+
var baseSchemaName = new DataColumn(SchemaTableColumn.BaseSchemaName, typeof(string));
307+
var baseTableName = new DataColumn(SchemaTableColumn.BaseTableName, typeof(string));
308+
var baseColumnName = new DataColumn(SchemaTableColumn.BaseColumnName, typeof(string));
309+
var isAliased = new DataColumn(SchemaTableColumn.IsAliased, typeof(bool));
310+
var isExpression = new DataColumn(SchemaTableColumn.IsExpression, typeof(bool));
311+
var isIdentity = new DataColumn("IsIdentity", typeof(bool));
312+
ordinal.DefaultValue = 0;
313+
precision.DefaultValue = 0;
314+
scale.DefaultValue = 0;
315+
isLong.DefaultValue = false;
316+
317+
// must maintain order for backward compatibility
318+
var columns = schemaTable.Columns;
319+
columns.Add(columnName);
320+
columns.Add(ordinal);
321+
columns.Add(size);
322+
columns.Add(precision);
323+
columns.Add(scale);
324+
columns.Add(isUnique);
325+
columns.Add(isKey);
326+
columns.Add(baseCatalogName);
327+
columns.Add(baseColumnName);
328+
columns.Add(baseSchemaName);
329+
columns.Add(baseTableName);
330+
columns.Add(dataType);
331+
columns.Add(allowDBNull);
332+
columns.Add(providerType);
333+
columns.Add(isAliased);
334+
columns.Add(isExpression);
335+
columns.Add(isIdentity);
336+
columns.Add(isAutoIncrement);
337+
columns.Add(isRowVersion);
338+
columns.Add(isHidden);
339+
columns.Add(isLong);
340+
columns.Add(isReadOnly);
341+
342+
for (var i = 0; i < colDefinitions.Length; ++i)
343+
{
344+
var col = colDefinitions[i];
345+
var schemaRow = schemaTable.NewRow();
346+
schemaRow[columnName] = col.Name;
347+
schemaRow[ordinal] = i + 1; // https://bugs.mysql.com/bug.php?id=61477
348+
schemaRow[dataType] = GetFieldType(i);
349+
schemaRow[size] = (Type)schemaRow[dataType] == typeof(string) || (Type)schemaRow[dataType] == typeof(Guid) ?
350+
col.ColumnLength / SerializationUtility.GetBytesPerCharacter(col.CharacterSet) :
351+
col.ColumnLength;
352+
schemaRow[providerType] = col.ColumnType;
353+
schemaRow[isLong] = col.ColumnLength > 255 && ((col.ColumnFlags & ColumnFlags.Blob) != 0 || col.ColumnType.IsBlob());
354+
schemaRow[isKey] = (col.ColumnFlags & ColumnFlags.PrimaryKey) != 0;
355+
schemaRow[allowDBNull] = (col.ColumnFlags & ColumnFlags.NotNull) == 0;
356+
schemaRow[scale] = col.Decimals;
357+
if (col.ColumnType.IsDecimal())
358+
{
359+
schemaRow[precision] = col.ColumnLength - 2 + ((col.ColumnFlags & ColumnFlags.Unsigned) != 0 ? 1 : 0);
360+
}
361+
362+
schemaRow[baseSchemaName] = col.SchemaName;
363+
schemaRow[baseTableName] = col.PhysicalTable;
364+
schemaRow[baseColumnName] = col.PhysicalName;
365+
schemaRow[isUnique] = false;
366+
schemaRow[isRowVersion] = false;
367+
schemaRow[isReadOnly] = false;
368+
369+
// To be consist with Orcale MySQL connector, set baseCatelogName to null and do not set isAliased, isExpression, isIdentity, isHidden value.
370+
schemaRow[baseCatalogName] = null;
371+
372+
schemaTable.Rows.Add(schemaRow);
373+
schemaRow.AcceptChanges();
374+
}
375+
376+
return schemaTable;
377+
}
378+
#endif
379+
273380
private MySqlDataReader(MySqlCommand command, CommandBehavior behavior)
274381
{
275382
Command = command;
@@ -328,6 +435,9 @@ private ResultSet GetResultSet()
328435
int m_recordsAffected;
329436
ResultSet m_resultSet;
330437
ResultSet m_resultSetBuffered;
438+
#if !NETSTANDARD1_3
439+
DataTable m_schemaTable;
440+
#endif
331441
readonly Queue<ResultSet> m_nextResultSetBuffer = new Queue<ResultSet>();
332442
}
333443
}

src/MySqlConnector/Serialization/ColumnDefinitionPayload.cs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,37 @@ namespace MySql.Data.Serialization
55
{
66
internal class ColumnDefinitionPayload
77
{
8-
public string Name { get; }
9-
public CharacterSet CharacterSet { get; }
10-
public int ColumnLength { get; }
11-
public ColumnType ColumnType { get; }
12-
public ColumnFlags ColumnFlags { get; }
8+
public string Name { get; private set; }
9+
10+
public CharacterSet CharacterSet { get; private set; }
11+
12+
public int ColumnLength { get; private set; }
13+
14+
public ColumnType ColumnType { get; private set; }
15+
16+
public ColumnFlags ColumnFlags { get; private set; }
17+
18+
public string SchemaName { get; private set; }
19+
20+
public string CatelogName { get; private set; }
21+
22+
public string Table { get; private set; }
23+
24+
public string PhysicalTable { get; private set; }
25+
26+
public string PhysicalName { get; private set; }
27+
28+
public byte Decimals { get; private set; }
1329

1430
public static ColumnDefinitionPayload Create(PayloadData payload)
1531
{
1632
var reader = new ByteArrayReader(payload.ArraySegment);
17-
var catalog = reader.ReadLengthEncodedByteString();
18-
var schema = reader.ReadLengthEncodedByteString();
19-
var table = reader.ReadLengthEncodedByteString();
20-
var physicalTable = reader.ReadLengthEncodedByteString();
33+
var catalog = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
34+
var schema = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
35+
var table = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
36+
var physicalTable = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
2137
var name = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
22-
var physicalName = reader.ReadLengthEncodedByteString();
38+
var physicalName = Encoding.UTF8.GetString(reader.ReadLengthEncodedByteString());
2339
reader.ReadByte(0x0C); // length of fixed-length fields, always 0x0C
2440
var characterSet = (CharacterSet) reader.ReadUInt16();
2541
var columnLength = (int) reader.ReadUInt32();
@@ -35,17 +51,25 @@ public static ColumnDefinitionPayload Create(PayloadData payload)
3551
}
3652

3753
if (reader.BytesRemaining != 0)
54+
{
3855
throw new FormatException("Extra bytes at end of payload.");
39-
return new ColumnDefinitionPayload(name, characterSet, columnLength, columnType, columnFlags);
40-
}
56+
}
57+
58+
return new ColumnDefinitionPayload
59+
{
60+
Name = name,
61+
CharacterSet = characterSet,
62+
ColumnLength = columnLength,
63+
ColumnType = columnType,
64+
ColumnFlags = columnFlags,
65+
SchemaName = schema,
66+
CatelogName = catalog,
67+
Table = table,
68+
PhysicalTable = physicalTable,
69+
PhysicalName = physicalName,
70+
Decimals = decimals
71+
};
4172

42-
private ColumnDefinitionPayload(string name, CharacterSet characterSet, int columnLength, ColumnType columnType, ColumnFlags columnFlags)
43-
{
44-
Name = name;
45-
CharacterSet = characterSet;
46-
ColumnLength = columnLength;
47-
ColumnType = columnType;
48-
ColumnFlags = columnFlags;
4973
}
5074
}
5175
}

src/MySqlConnector/Serialization/ColumnType.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace MySql.Data.Serialization
1+
namespace MySql.Data.Serialization
22
{
33
/// <summary>
44
/// See <a href="https://dev.mysql.com/doc/internals/en/com-query-response.html#column-type">MySQL documentation</a>.
@@ -36,4 +36,22 @@ internal enum ColumnType
3636
String = 0xFE,
3737
Geometry = 0xFF,
3838
}
39+
40+
internal static class Extensions
41+
{
42+
public static bool IsBlob(this ColumnType type)
43+
{
44+
return type >= ColumnType.TinyBlob && type <= ColumnType.Blob;
45+
}
46+
47+
public static bool IsString(this ColumnType type)
48+
{
49+
return type == ColumnType.VarChar || type == ColumnType.VarString || type == ColumnType.String;
50+
}
51+
52+
public static bool IsDecimal(this ColumnType type)
53+
{
54+
return type == ColumnType.Decimal || type == ColumnType.NewDecimal;
55+
}
56+
}
3957
}

tests/SideBySide/DataTypes.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,52 @@ public void InsertLargeBlobSync(string column, int size)
598598
}
599599
}
600600

601+
#if !NETCOREAPP1_1_2
602+
[Theory]
603+
[InlineData("rowid", "datatypes_bools", 11, typeof(int), false, true, false, 0, 0)]
604+
[InlineData("Boolean", "datatypes_bools", 1, typeof(bool), false, false, true, 0, 0)]
605+
[InlineData("SByte", "datatypes_integers", 4, typeof(sbyte), false, false, true, 0, 0)]
606+
[InlineData("Int16", "datatypes_integers", 6, typeof(short), false, false, true, 0, 0)]
607+
[InlineData("UInt16", "datatypes_integers", 5, typeof(ushort), false, false, true, 0, 0)]
608+
[InlineData("Double", "datatypes_reals", 22, typeof(double), false, false, true, 0, 31)]
609+
[InlineData("MediumDecimal", "datatypes_reals", 30, typeof(decimal), false, false, true, 28, 8)]
610+
[InlineData("utf8", "datatypes_strings", 300, typeof(string), false, false, true, 0, 0)]
611+
#if !BASELINE
612+
[InlineData("guid", "datatypes_strings", 36, typeof(Guid), false, false, true, 0, 0)]
613+
[InlineData("guidbin", "datatypes_strings", 36, typeof(Guid), false, false, true, 0, 0)]
614+
[InlineData("Blob", "datatypes_blobs", 65535, typeof(byte[]), true, false, true, 0, 0)]
615+
#endif
616+
[InlineData("Date", "datatypes_times", 10, typeof(DateTime), false, false, true, 0, 0)]
617+
[InlineData("Time", "datatypes_times", 17, typeof(TimeSpan), false, false, true, 0, 6)]
618+
public void GetSchemaTable(string column, string table, int columnSize, Type dataType, bool isLong, bool isKey, bool allowDbNull, int precision, int scale)
619+
{
620+
using (var command = m_database.Connection.CreateCommand())
621+
{
622+
command.CommandText = $"select 1, `{column}` from `{table}`;";
623+
using (var reader = command.ExecuteReader())
624+
{
625+
var schema = reader.GetSchemaTable().Rows[1];
626+
Assert.Equal(column, schema["ColumnName"]);
627+
int ordinal = 2; // https://bugs.mysql.com/bug.php?id=61477
628+
Assert.Equal(ordinal, schema["ColumnOrdinal"]);
629+
Assert.Equal(dataType, schema["DataType"]);
630+
Assert.Equal(columnSize, schema["ColumnSize"]);
631+
Assert.Equal(isLong, schema["IsLong"]);
632+
Assert.Equal(isKey, schema["IsKey"]);
633+
Assert.Equal(allowDbNull, schema["AllowDBNull"]);
634+
Assert.Equal(precision, schema["NumericPrecision"]);
635+
Assert.Equal(scale, schema["NumericScale"]);
636+
Assert.Equal(m_database.Connection.Database, schema["BaseSchemaName"]);
637+
Assert.Equal(table, schema["BaseTableName"]);
638+
Assert.Equal(column, schema["BaseColumnName"]);
639+
Assert.False((bool) schema["IsUnique"]);
640+
Assert.False((bool) schema["IsRowVersion"]);
641+
Assert.False((bool) schema["IsReadOnly"]);
642+
}
643+
}
644+
}
645+
#endif
646+
601647
private static byte[] CreateByteArray(int size)
602648
{
603649
var data = new byte[size];

tests/SideBySide/SideBySide.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<!--testing packages-->
2525
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
2626
<PackageReference Include="xunit" Version="2.3.0-beta5-build3769" />
27+
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta5-build3769" />
2728
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta5-build3769" />
2829
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta5-build3769" />
2930
<!--app packages-->

0 commit comments

Comments
 (0)