Skip to content

Commit 9d74878

Browse files
authored
Merge pull request #371 from bgrainger/get-schema
Improve GetSchema. Fixes #361
2 parents 4cbcd94 + b556416 commit 9d74878

File tree

6 files changed

+269
-130
lines changed

6 files changed

+269
-130
lines changed

src/MySqlConnector/MySqlClient/MySqlConnection.cs

Lines changed: 12 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -217,96 +217,23 @@ private static async Task ClearPoolAsync(MySqlConnection connection, IOBehavior
217217
#if !NETSTANDARD1_3
218218
protected override DbProviderFactory DbProviderFactory => MySqlClientFactory.Instance;
219219

220-
/// <summary>
221-
/// System.Data.Common, initial implementation of API DBConnection.GetSchema(String)
222-
/// Returns schema information for the data source of this DbConnection using the specified string for the schema name.
223-
/// </summary>
224-
/// <param name="collectionName">Specifies the name of the schema to return.</param>
225-
/// <returns>A DataTable that contains schema information.</returns>
226-
public override System.Data.DataTable GetSchema(string collectionName)
227-
{
228-
return GetSchema(collectionName, null);
229-
}
220+
/// <inheritdoc cref="DbConnection.GetSchema()"/>
221+
public override DataTable GetSchema() => GetSchemaProvider().GetSchema();
230222

231-
232-
/// <summary>
233-
/// System.Data.Common, initial implementation of API DBConnection.GetSchema(String)
234-
/// Returns schema information for the data source of this DbConnection using the specified string for the schema name.
235-
/// </summary>
236-
/// <param name="collectionName">Specifies the name of the schema to return.</param>
237-
/// <param name="restrictions">Restrictions not supported yet.</param>
238-
/// <returns>A DataTable that contains schema information.</returns>
239-
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictions)
240-
{
241-
var dt = new DataTable(collectionName);
242-
switch (collectionName)
243-
{
244-
case "DataTypes":
245-
dt.Columns.AddRange(new[] { // The names come from DbMetaDataColumnNames
246-
new DataColumn("DataType", typeof(string)),
247-
new DataColumn("TypeName", typeof(string)),
248-
new DataColumn("ProviderDbType", typeof(int)),
249-
new DataColumn("IsUnsigned", typeof(bool))
250-
});
251-
252-
// Column type mappings:
253-
var colTypes = Types.TypeMapper.Instance.GetColumnMappings();
254-
foreach (var map in colTypes)
255-
{
256-
var dbTypeMap = map.DbTypeMapping;
257-
var dbType = dbTypeMap.DbTypes.FirstOrDefault();
258-
dt.Rows.Add(new object[] {
259-
dbTypeMap.ClrType.FullName,
260-
map.DataTypeName,
261-
(int)dbType,
262-
map.Unsigned
263-
});
264-
}
223+
/// <inheritdoc cref="DbConnection.GetSchema(string)"/>
224+
public override DataTable GetSchema(string collectionName) => GetSchemaProvider().GetSchema(collectionName);
265225

266-
// Data type mappings:
267-
foreach (MySqlDbType mapItem in Enum.GetValues(typeof(MySqlDbType)))
268-
{
269-
var typeName = Enum.GetName(typeof(MySqlDbType), mapItem);
270-
var dbType = Types.TypeMapper.Instance.GetDbTypeForMySqlDbType(mapItem);
271-
var map = Types.TypeMapper.Instance.GetDbTypeMapping(dbType);
272-
if (map != null) // MySqlDbType.Set is not supported by the mapper.
273-
{
274-
dt.Rows.Add(new object[] {
275-
map.ClrType.FullName,
276-
Enum.GetName(typeof(MySqlDbType), mapItem).ToLower(),
277-
(int)dbType,
278-
typeName.Contains("UInt") || typeName.Contains("UByte")
279-
});
280-
}
281-
}
282-
return dt;
283-
284-
case "Procedures":
285-
dt.Columns.AddRange(new[] {
286-
new DataColumn("ROUTINE_TYPE"),
287-
new DataColumn("ROUTINE_SCHEMA"),
288-
new DataColumn("SPECIFIC_NAME")
289-
});
290-
var procsQuery = "SELECT ROUTINE_TYPE, ROUTINE_SCHEMA, SPECIFIC_NAME FROM INFORMATION_SCHEMA.ROUTINES;";
291-
if (m_connectionState != ConnectionState.Open)
292-
{
293-
Open();
294-
}
295-
using (var com = new MySqlCommand(procsQuery, this))
296-
using (var reader = com.ExecuteReader())
297-
{
298-
while (reader.Read())
299-
{
300-
dt.Rows.Add(new object[] { reader.GetString(0), reader.GetString(1), reader.GetString(2) });
301-
}
302-
}
303-
return dt;
226+
/// <inheritdoc cref="DbConnection.GetSchema(string)"/>
227+
public override DataTable GetSchema(string collectionName, string[] restrictions) => GetSchemaProvider().GetSchema(collectionName);
304228

305-
default:
306-
throw new NotImplementedException("Not yet supported: GetSchema(\"" + collectionName + "\"). Please send a PR.");
307-
}
229+
private SchemaProvider GetSchemaProvider()
230+
{
231+
if (m_schemaProvider == null)
232+
m_schemaProvider = new SchemaProvider(this);
233+
return m_schemaProvider;
308234
}
309235

236+
SchemaProvider m_schemaProvider;
310237
#endif
311238

312239
public override int ConnectionTimeout => m_connectionSettings.ConnectionTimeout;
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#if !NETSTANDARD1_3
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Data;
5+
using System.Linq;
6+
using MySql.Data.MySqlClient.Types;
7+
8+
namespace MySql.Data.MySqlClient
9+
{
10+
internal sealed class SchemaProvider
11+
{
12+
public SchemaProvider(MySqlConnection connection)
13+
{
14+
m_connection = connection;
15+
m_schemaCollections = new Dictionary<string, Action<DataTable>>
16+
{
17+
{ "MetaDataCollections", FillMetadataCollections },
18+
{ "DataTypes", FillDataTypes },
19+
{ "Procedures", FillProcedures }
20+
};
21+
}
22+
23+
public DataTable GetSchema() => GetSchema("MetaDataCollections");
24+
25+
public DataTable GetSchema(string collectionName)
26+
{
27+
if (!m_schemaCollections.TryGetValue(collectionName, out var fillAction))
28+
throw new ArgumentException("Invalid collection name.", nameof(collectionName));
29+
30+
var dataTable = new DataTable(collectionName);
31+
fillAction(dataTable);
32+
return dataTable;
33+
}
34+
35+
private void FillMetadataCollections(DataTable dataTable)
36+
{
37+
dataTable.Columns.AddRange(new[] {
38+
new DataColumn("CollectionName", typeof(string)),
39+
new DataColumn("NumberOfRestrictions", typeof(int)),
40+
new DataColumn("NumberOfIdentifierParts", typeof(int))
41+
});
42+
43+
foreach (var collectionName in m_schemaCollections.Keys)
44+
dataTable.Rows.Add(collectionName, 0, 0);
45+
}
46+
47+
private void FillDataTypes(DataTable dataTable)
48+
{
49+
dataTable.Columns.AddRange(new[]
50+
{
51+
new DataColumn("TypeName", typeof(string)),
52+
new DataColumn("ProviderDbType", typeof(int)),
53+
new DataColumn("ColumnSize", typeof(long)),
54+
new DataColumn("CreateFormat", typeof(string)),
55+
new DataColumn("CreateParameters", typeof(string)),
56+
new DataColumn("DataType", typeof(string)),
57+
new DataColumn("IsAutoIncrementable", typeof(bool)),
58+
new DataColumn("IsBestMatch", typeof(bool)),
59+
new DataColumn("IsCaseSensitive", typeof(bool)),
60+
new DataColumn("IsFixedLength", typeof(bool)),
61+
new DataColumn("IsFixedPrecisionScale", typeof(bool)),
62+
new DataColumn("IsLong", typeof(bool)),
63+
new DataColumn("IsNullable", typeof(bool)),
64+
new DataColumn("IsSearchable", typeof(bool)),
65+
new DataColumn("IsSearchableWithLike", typeof(bool)),
66+
new DataColumn("IsUnsigned", typeof(bool)),
67+
new DataColumn("MaximumScale", typeof(short)),
68+
new DataColumn("MinimumScale", typeof(short)),
69+
new DataColumn("IsConcurrencyType", typeof(bool)),
70+
new DataColumn("IsLiteralSupported", typeof(bool)),
71+
new DataColumn("LiteralPrefix", typeof(string)),
72+
new DataColumn("LiteralSuffix", typeof(string)),
73+
new DataColumn("NativeDataType", typeof(string)),
74+
});
75+
76+
var clrTypes = new HashSet<string>();
77+
foreach (var columnType in TypeMapper.Instance.GetColumnTypeMetadata())
78+
{
79+
// hard-code a few types to not appear in the schema table
80+
var mySqlDbType = columnType.MySqlDbType;
81+
if (mySqlDbType == MySqlDbType.Decimal || mySqlDbType == MySqlDbType.Newdate || mySqlDbType == MySqlDbType.Null || mySqlDbType == MySqlDbType.VarString)
82+
continue;
83+
if (mySqlDbType == MySqlDbType.Bool && columnType.IsUnsigned)
84+
continue;
85+
86+
// set miscellaneous properties in code (rather than being data-driven)
87+
var clrType = columnType.DbTypeMapping.ClrType;
88+
var clrTypeName = clrType.ToString();
89+
var dataTypeName = mySqlDbType == MySqlDbType.Guid ? "GUID" :
90+
mySqlDbType == MySqlDbType.Bool ? "BOOL" : columnType.DataTypeName;
91+
var isAutoIncrementable = mySqlDbType == MySqlDbType.Byte || mySqlDbType == MySqlDbType.Int16 || mySqlDbType == MySqlDbType.Int24 || mySqlDbType == MySqlDbType.Int32 || mySqlDbType == MySqlDbType.Int64 ||
92+
mySqlDbType == MySqlDbType.UByte || mySqlDbType == MySqlDbType.UInt16 || mySqlDbType == MySqlDbType.UInt24 || mySqlDbType == MySqlDbType.UInt32 || mySqlDbType == MySqlDbType.UInt64;
93+
var isBestMatch = clrTypes.Add(clrTypeName);
94+
var isFixedLength = isAutoIncrementable ||
95+
mySqlDbType == MySqlDbType.Date || mySqlDbType == MySqlDbType.DateTime || mySqlDbType == MySqlDbType.Time || mySqlDbType == MySqlDbType.Timestamp ||
96+
mySqlDbType == MySqlDbType.Double || mySqlDbType == MySqlDbType.Float || mySqlDbType == MySqlDbType.Year || mySqlDbType == MySqlDbType.Guid || mySqlDbType == MySqlDbType.Bool;
97+
var isFixedPrecisionScale = isFixedLength ||
98+
mySqlDbType == MySqlDbType.Bit || mySqlDbType == MySqlDbType.NewDecimal;
99+
var isLong = mySqlDbType == MySqlDbType.Blob || mySqlDbType == MySqlDbType.MediumBlob || mySqlDbType == MySqlDbType.LongBlob;
100+
101+
// map ColumnTypeMetadata to the row for this data type
102+
var createFormatParts = columnType.CreateFormat.Split(';');
103+
dataTable.Rows.Add(
104+
dataTypeName,
105+
(int) mySqlDbType,
106+
columnType.ColumnSize,
107+
createFormatParts[0],
108+
createFormatParts.Length == 1 ? null : createFormatParts[1],
109+
clrTypeName,
110+
isAutoIncrementable,
111+
isBestMatch,
112+
false,
113+
isFixedLength,
114+
isFixedPrecisionScale,
115+
isLong,
116+
true,
117+
clrType != typeof(byte[]),
118+
clrType == typeof(string),
119+
columnType.IsUnsigned,
120+
DBNull.Value,
121+
DBNull.Value,
122+
DBNull.Value,
123+
true,
124+
DBNull.Value,
125+
DBNull.Value,
126+
null
127+
);
128+
}
129+
}
130+
131+
private void FillProcedures(DataTable dataTable)
132+
{
133+
dataTable.Columns.AddRange(new[]
134+
{
135+
new DataColumn("SPECIFIC_NAME", typeof(string)),
136+
new DataColumn("ROUTINE_CATALOG", typeof(string)),
137+
new DataColumn("ROUTINE_SCHEMA", typeof(string)),
138+
new DataColumn("ROUTINE_NAME", typeof(string)),
139+
new DataColumn("ROUTINE_TYPE", typeof(string)),
140+
new DataColumn("DTD_IDENTIFIER", typeof(string)),
141+
new DataColumn("ROUTINE_BODY", typeof(string)),
142+
new DataColumn("ROUTINE_DEFINITION", typeof(string)),
143+
new DataColumn("EXTERNAL_NAME", typeof(string)),
144+
new DataColumn("EXTERNAL_LANGUAGE", typeof(string)),
145+
new DataColumn("PARAMETER_STYLE", typeof(string)),
146+
new DataColumn("IS_DETERMINISTIC", typeof(string)),
147+
new DataColumn("SQL_DATA_ACCESS", typeof(string)),
148+
new DataColumn("SQL_PATH", typeof(string)),
149+
new DataColumn("SECURITY_TYPE", typeof(string)),
150+
new DataColumn("CREATED", typeof(DateTime)),
151+
new DataColumn("LAST_ALTERED", typeof(DateTime)),
152+
new DataColumn("SQL_MODE", typeof(string)),
153+
new DataColumn("ROUTINE_COMMENT", typeof(string)),
154+
new DataColumn("DEFINER", typeof(string)),
155+
});
156+
157+
Action close = null;
158+
if (m_connection.State != ConnectionState.Open)
159+
{
160+
m_connection.Open();
161+
close = m_connection.Close;
162+
}
163+
164+
using (var command = m_connection.CreateCommand())
165+
{
166+
command.CommandText = "SELECT " + string.Join(", ", dataTable.Columns.Cast<DataColumn>().Select(x => x.ColumnName)) + " FROM INFORMATION_SCHEMA.ROUTINES;";
167+
using (var reader = command.ExecuteReader())
168+
{
169+
while (reader.Read())
170+
{
171+
var rowValues = new object[dataTable.Columns.Count];
172+
reader.GetValues(rowValues);
173+
dataTable.Rows.Add(rowValues);
174+
}
175+
}
176+
}
177+
178+
close?.Invoke();
179+
}
180+
181+
readonly MySqlConnection m_connection;
182+
readonly Dictionary<string, Action<DataTable>> m_schemaCollections;
183+
}
184+
}
185+
#endif

src/MySqlConnector/MySqlClient/Types/ColumnTypeMetadata.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,29 @@ internal sealed class ColumnTypeMetadata
44
{
55
public static string CreateLookupKey(string columnTypeName, bool isUnsigned, int length) => $"{columnTypeName}|{(isUnsigned ? "u" : "s")}|{length}";
66

7-
public ColumnTypeMetadata(string dataTypeName, DbTypeMapping dbTypeMapping, MySqlDbType mySqlDbType, bool unsigned = false, bool binary = false, int length = 0, string simpleDataTypeName = null)
7+
public 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)
88
{
99
DataTypeName = dataTypeName;
1010
SimpleDataTypeName = simpleDataTypeName ?? dataTypeName;
11+
CreateFormat = createFormat ?? (dataTypeName + (isUnsigned ? " UNSIGNED" : ""));
1112
DbTypeMapping = dbTypeMapping;
1213
MySqlDbType = mySqlDbType;
13-
Unsigned = unsigned;
14+
ColumnSize = columnSize;
15+
IsUnsigned = isUnsigned;
1416
Binary = binary;
1517
Length = length;
1618
}
1719

1820
public string DataTypeName { get; }
1921
public string SimpleDataTypeName { get; }
22+
public string CreateFormat { get; }
2023
public DbTypeMapping DbTypeMapping { get; }
2124
public MySqlDbType MySqlDbType { get; }
2225
public bool Binary { get; }
23-
public bool Unsigned { get; }
26+
public long ColumnSize { get; }
27+
public bool IsUnsigned { get; }
2428
public int Length { get; }
2529

26-
public string CreateLookupKey() => CreateLookupKey(DataTypeName, Unsigned, Length);
30+
public string CreateLookupKey() => CreateLookupKey(DataTypeName, IsUnsigned, Length);
2731
}
2832
}

0 commit comments

Comments
 (0)