Skip to content

Commit a9eb0eb

Browse files
committed
Implement DeriveParameters. Fixes #419
1 parent b899dde commit a9eb0eb

File tree

5 files changed

+164
-8
lines changed

5 files changed

+164
-8
lines changed

src/MySqlConnector/Core/CachedProcedure.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace MySqlConnector.Core
1212
{
1313
internal sealed class CachedProcedure
1414
{
15-
internal static async Task<CachedProcedure> FillAsync(IOBehavior ioBehavior, MySqlConnection connection, string schema, string component, CancellationToken cancellationToken)
15+
public static async Task<CachedProcedure> FillAsync(IOBehavior ioBehavior, MySqlConnection connection, string schema, string component, CancellationToken cancellationToken)
1616
{
1717
var parameters = new List<CachedParameter>();
1818
using (var cmd = connection.CreateCommand())
@@ -51,19 +51,21 @@ FROM information_schema.parameters
5151
return parameters.Count == 0 ? null : new CachedProcedure(schema, component, parameters.AsReadOnly());
5252
}
5353

54+
public ReadOnlyCollection<CachedParameter> Parameters { get; }
55+
5456
private CachedProcedure(string schema, string component, ReadOnlyCollection<CachedParameter> parameters)
5557
{
5658
m_schema = schema;
5759
m_component = component;
58-
m_parameters = parameters;
60+
Parameters = parameters;
5961
}
6062

6163
internal MySqlParameterCollection AlignParamsWithDb(MySqlParameterCollection parameterCollection)
6264
{
6365
var alignedParams = new MySqlParameterCollection();
6466
var returnParam = parameterCollection.FirstOrDefault(x => x.Direction == ParameterDirection.ReturnValue);
6567

66-
foreach (var cachedParam in m_parameters)
68+
foreach (var cachedParam in Parameters)
6769
{
6870
MySqlParameter alignParam;
6971
if (cachedParam.Direction == ParameterDirection.ReturnValue)
@@ -92,6 +94,5 @@ internal MySqlParameterCollection AlignParamsWithDb(MySqlParameterCollection par
9294

9395
readonly string m_schema;
9496
readonly string m_component;
95-
readonly ReadOnlyCollection<CachedParameter> m_parameters;
9697
}
9798
}

src/MySqlConnector/MySql.Data.MySqlClient/MySqlCommandBuilder.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,50 @@
1-
#if !NETSTANDARD1_3
21
using System;
32
using System.Data;
43
using System.Data.Common;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using MySqlConnector.Core;
7+
using MySqlConnector.Protocol.Serialization;
58
using MySqlConnector.Utilities;
69

710
namespace MySql.Data.MySqlClient
811
{
12+
#if !NETSTANDARD1_3
913
public class MySqlCommandBuilder : DbCommandBuilder
14+
#else
15+
public static class MySqlCommandBuilder
16+
#endif
1017
{
18+
public static void DeriveParameters(MySqlCommand command) => DeriveParametersAsync(IOBehavior.Synchronous, command, CancellationToken.None).GetAwaiter().GetResult();
19+
public static Task DeriveParametersAsync(MySqlCommand command) => DeriveParametersAsync(command?.Connection?.AsyncIOBehavior ?? IOBehavior.Asynchronous, command, CancellationToken.None);
20+
public static Task DeriveParametersAsync(MySqlCommand command, CancellationToken cancellationToken) => DeriveParametersAsync(command?.Connection?.AsyncIOBehavior ?? IOBehavior.Asynchronous, command, cancellationToken);
21+
22+
private static async Task DeriveParametersAsync(IOBehavior ioBehavior, MySqlCommand command, CancellationToken cancellationToken)
23+
{
24+
if (command == null)
25+
throw new ArgumentNullException(nameof(command));
26+
if (command.CommandType != CommandType.StoredProcedure)
27+
throw new ArgumentException("MySqlCommand.CommandType must be StoredProcedure not {0}".FormatInvariant(command.CommandType), nameof(command));
28+
if (string.IsNullOrWhiteSpace(command.CommandText))
29+
throw new ArgumentException("MySqlCommand.CommandText must be set to a stored procedure name", nameof(command));
30+
if (command.Connection?.State != ConnectionState.Open)
31+
throw new ArgumentException("MySqlCommand.Connection must be an open connection.", nameof(command));
32+
if (command.Connection.Session.ServerVersion.Version < ServerVersions.SupportsProcedureCache)
33+
throw new NotSupportedException("MySQL Server {0} doesn't support INFORMATION_SCHEMA".FormatInvariant(command.Connection.Session.ServerVersion.OriginalString));
34+
35+
var cachedProcedure = await command.Connection.GetCachedProcedure(ioBehavior, command.CommandText, cancellationToken).ConfigureAwait(false);
36+
command.Parameters.Clear();
37+
if (cachedProcedure != null)
38+
{
39+
foreach (var cachedParameter in cachedProcedure.Parameters)
40+
{
41+
var parameter = command.Parameters.Add("@" + cachedParameter.Name, cachedParameter.MySqlDbType);
42+
parameter.Direction = cachedParameter.Direction;
43+
}
44+
}
45+
}
46+
47+
#if !NETSTANDARD1_3
1148
public MySqlCommandBuilder()
1249
{
1350
QuotePrefix = "`";
@@ -60,6 +97,6 @@ public override string UnquoteIdentifier(string quotedIdentifier)
6097
}
6198

6299
private void RowUpdatingHandler(object sender, MySqlRowUpdatingEventArgs e) => RowUpdatingHandler(e);
100+
#endif
63101
}
64102
}
65-
#endif

tests/SideBySide/CommandBuilderTests.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#if !NETCOREAPP1_1_2
21
using System;
32
using System.Data;
43
using Dapper;
@@ -20,6 +19,43 @@ public void Dispose()
2019
m_database.Connection.Close();
2120
}
2221

22+
[SkippableFact(Baseline = "Throws NullReferenceException")]
23+
public void DeriveParametersNull()
24+
{
25+
Assert.Throws<ArgumentNullException>(() => MySqlCommandBuilder.DeriveParameters(null));
26+
}
27+
28+
[SkippableFact(Baseline = "Throws NullReferenceException")]
29+
public void DeriveParametersNoConnection()
30+
{
31+
using (var cmd = new MySqlCommand("test"))
32+
{
33+
cmd.CommandType = CommandType.StoredProcedure;
34+
Assert.Throws<ArgumentException>(() => MySqlCommandBuilder.DeriveParameters(cmd));
35+
}
36+
}
37+
38+
[SkippableFact(Baseline = "Throws InvalidOperationException")]
39+
public void DeriveParametersText()
40+
{
41+
using (var cmd = m_database.Connection.CreateCommand())
42+
{
43+
cmd.CommandText = "select 1;";
44+
Assert.Throws<ArgumentException>(() => MySqlCommandBuilder.DeriveParameters(cmd));
45+
}
46+
}
47+
48+
[SkippableFact(Baseline = "Throws MySqlException")]
49+
public void DeriveParametersNoCommandText()
50+
{
51+
using (var cmd = m_database.Connection.CreateCommand())
52+
{
53+
cmd.CommandType = CommandType.StoredProcedure;
54+
Assert.Throws<ArgumentException>(() => MySqlCommandBuilder.DeriveParameters(cmd));
55+
}
56+
}
57+
58+
#if !NETCOREAPP1_1_2
2359
[Fact]
2460
public void Insert()
2561
{
@@ -120,8 +156,8 @@ public void UnquoteIdentifier(string input, string expected)
120156
var cb = new MySqlCommandBuilder();
121157
Assert.Equal(expected, cb.UnquoteIdentifier(input));
122158
}
159+
#endif
123160

124161
readonly DatabaseFixture m_database;
125162
}
126163
}
127-
#endif

tests/SideBySide/StoredProcedureFixture.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ create procedure number_lister (inout high int)
109109
begin
110110
select 1, 2, 3;
111111
end;");
112+
113+
if (AppConfig.SupportsJson)
114+
{
115+
Connection.Execute(@"drop procedure if exists SetJson;
116+
CREATE PROCEDURE `SetJson`(vJson JSON)
117+
BEGIN
118+
SELECT vJson;
119+
END
120+
");
121+
}
112122
}
113123
}
114124
}

tests/SideBySide/StoredProcedureTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,78 @@ public async Task DottedName(bool useDatabaseName)
451451
}
452452
}
453453

454+
[Fact]
455+
public void DeriveParametersCircle()
456+
{
457+
using (var cmd = new MySqlCommand("circle", m_database.Connection))
458+
{
459+
cmd.CommandType = CommandType.StoredProcedure;
460+
MySqlCommandBuilder.DeriveParameters(cmd);
461+
462+
Assert.Collection(cmd.Parameters.Cast<MySqlParameter>(),
463+
AssertParameter("@radius", ParameterDirection.Input, MySqlDbType.Double),
464+
AssertParameter("@height", ParameterDirection.Input, MySqlDbType.Double),
465+
AssertParameter("@name", ParameterDirection.Input, MySqlDbType.VarChar),
466+
AssertParameter("@diameter", ParameterDirection.Output, MySqlDbType.Double),
467+
AssertParameter("@circumference", ParameterDirection.Output, MySqlDbType.Double),
468+
AssertParameter("@area", ParameterDirection.Output, MySqlDbType.Double),
469+
AssertParameter("@volume", ParameterDirection.Output, MySqlDbType.Double),
470+
AssertParameter("@shape", ParameterDirection.Output, MySqlDbType.VarChar));
471+
}
472+
}
473+
474+
[Fact]
475+
public void DeriveParametersNumberLister()
476+
{
477+
using (var cmd = new MySqlCommand("number_lister", m_database.Connection))
478+
{
479+
cmd.CommandType = CommandType.StoredProcedure;
480+
MySqlCommandBuilder.DeriveParameters(cmd);
481+
482+
Assert.Collection(cmd.Parameters.Cast<MySqlParameter>(),
483+
AssertParameter("@high", ParameterDirection.InputOutput, MySqlDbType.Int32));
484+
}
485+
}
486+
487+
[Fact]
488+
public void DeriveParametersRemovesExisting()
489+
{
490+
using (var cmd = new MySqlCommand("number_lister", m_database.Connection))
491+
{
492+
cmd.CommandType = CommandType.StoredProcedure;
493+
cmd.Parameters.AddWithValue("test1", 1);
494+
cmd.Parameters.AddWithValue("test2", 2);
495+
cmd.Parameters.AddWithValue("test3", 3);
496+
497+
MySqlCommandBuilder.DeriveParameters(cmd);
498+
Assert.Collection(cmd.Parameters.Cast<MySqlParameter>(),
499+
AssertParameter("@high", ParameterDirection.InputOutput, MySqlDbType.Int32));
500+
}
501+
}
502+
503+
[SkippableFact(ServerFeatures.Json, Baseline = "https://bugs.mysql.com/bug.php?id=89335")]
504+
public void DeriveParametersSetJson()
505+
{
506+
using (var cmd = new MySqlCommand("SetJson", m_database.Connection))
507+
{
508+
cmd.CommandType = CommandType.StoredProcedure;
509+
MySqlCommandBuilder.DeriveParameters(cmd);
510+
511+
Assert.Collection(cmd.Parameters.Cast<MySqlParameter>(),
512+
AssertParameter("@vJson", ParameterDirection.Input, MySqlDbType.JSON));
513+
}
514+
}
515+
516+
private static Action<MySqlParameter> AssertParameter(string name, ParameterDirection direction, MySqlDbType mySqlDbType)
517+
{
518+
return x =>
519+
{
520+
Assert.Equal(name, x.ParameterName);
521+
Assert.Equal(direction, x.Direction);
522+
Assert.Equal(mySqlDbType, x.MySqlDbType);
523+
};
524+
}
525+
454526
#if !NETCOREAPP1_1_2
455527
[Theory]
456528
[InlineData("echof", "FUNCTION", "varchar(63)", "BEGIN RETURN name; END", "NO", "CONTAINS SQL")]

0 commit comments

Comments
 (0)