Skip to content

Commit 52f8183

Browse files
committed
Don't return data when there is no result set. Fixes #722
The result set that's used to return stored procedure output parameters is an implementation detail and must not be exposed to callers.
1 parent 5c53262 commit 52f8183

File tree

3 files changed

+46
-17
lines changed

3 files changed

+46
-17
lines changed

docs/content/tutorials/migrating-from-connector-net.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,4 @@ The following bugs in Connector/NET are fixed by switching to MySqlConnector. (~
208208
* [#96636](https://bugs.mysql.com/bug.php?id=96636): `MySqlConnection.Open()` slow under load when using SSL
209209
* [#96717](https://bugs.mysql.com/bug.php?id=96717): Not compatible with MySQL Server 5.0
210210
* [#97067](https://bugs.mysql.com/bug.php?id=97067): Aggregate functions on BIT(n) columns return wrong result
211+
* [#97300](https://bugs.mysql.com/bug.php?id=97300): `GetSchemaTable()` returns table for stored procedure with output parameters

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@ public override bool NextResult()
2929

3030
public override bool Read()
3131
{
32-
Command?.CancellableCommand.ResetCommandTimeout();
33-
return GetResultSet().Read();
32+
VerifyNotDisposed();
33+
Command!.CancellableCommand.ResetCommandTimeout();
34+
return m_resultSet!.Read();
3435
}
3536

3637
public override Task<bool> ReadAsync(CancellationToken cancellationToken)
3738
{
38-
Command?.CancellableCommand.ResetCommandTimeout();
39-
return GetResultSet().ReadAsync(cancellationToken);
39+
VerifyNotDisposed();
40+
Command!.CancellableCommand.ResetCommandTimeout();
41+
return m_resultSet!.ReadAsync(cancellationToken);
4042
}
4143

4244
internal Task<bool> ReadAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) =>
43-
GetResultSet().ReadAsync(ioBehavior, cancellationToken);
45+
m_resultSet!.ReadAsync(ioBehavior, cancellationToken);
4446

4547
public override Task<bool> NextResultAsync(CancellationToken cancellationToken)
4648
{
@@ -58,7 +60,7 @@ internal async Task<bool> NextResultAsync(IOBehavior ioBehavior, CancellationTok
5860
await m_resultSet!.ReadEntireAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
5961
await ScanResultSetAsync(ioBehavior, m_resultSet, cancellationToken).ConfigureAwait(false);
6062
if (m_hasMoreResults && m_resultSet.ContainsCommandParameters)
61-
await ReadOutParametersAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
63+
await ReadOutParametersAsync(Command!, m_resultSet, ioBehavior, cancellationToken).ConfigureAwait(false);
6264
else
6365
break;
6466
}
@@ -366,7 +368,7 @@ internal static async Task<MySqlDataReader> CreateAsync(CommandListPosition comm
366368
dataReader.m_hasMoreResults = true;
367369

368370
if (dataReader.m_resultSet.ContainsCommandParameters)
369-
await dataReader.ReadOutParametersAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
371+
await ReadOutParametersAsync(dataReader.Command!, dataReader.m_resultSet, ioBehavior, cancellationToken).ConfigureAwait(false);
370372

371373
// if the command list has multiple commands, keep reading until a result set is found
372374
while (dataReader.m_resultSet.State == ResultSetState.NoMoreData && commandListPosition.CommandIndex < commandListPosition.Commands.Count)
@@ -529,30 +531,31 @@ internal async ValueTask DisposeAsync(IOBehavior ioBehavior, CancellationToken c
529531
// If ResultSet.ContainsCommandParameters is true, then this method should be called to read the (single)
530532
// row in that result set, which contains the values of "out" parameters from the previous stored procedure
531533
// execution. These values will be stored in the parameters of the associated command.
532-
private async Task ReadOutParametersAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
534+
private static async Task ReadOutParametersAsync(IMySqlCommand command, ResultSet resultSet, IOBehavior ioBehavior, CancellationToken cancellationToken)
533535
{
534-
await ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
536+
await resultSet.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
535537

536-
if (GetString(0) != SingleCommandPayloadCreator.OutParameterSentinelColumnName)
538+
var row = resultSet.GetCurrentRow();
539+
if (row.GetString(0) != SingleCommandPayloadCreator.OutParameterSentinelColumnName)
537540
throw new InvalidOperationException("Expected out parameter values.");
538541

539-
for (var i = 0; i < Command!.OutParameters!.Count; i++)
542+
for (var i = 0; i < command.OutParameters!.Count; i++)
540543
{
541-
var param = Command.OutParameters[i];
544+
var param = command.OutParameters[i];
542545
var columnIndex = i + 1;
543-
if (param.HasSetDbType && !IsDBNull(columnIndex))
546+
if (param.HasSetDbType && !row.IsDBNull(columnIndex))
544547
{
545548
var dbTypeMapping = TypeMapper.Instance.GetDbTypeMapping(param.DbType);
546549
if (dbTypeMapping is object)
547550
{
548-
param.Value = dbTypeMapping.DoConversion(GetValue(columnIndex));
551+
param.Value = dbTypeMapping.DoConversion(row.GetValue(columnIndex));
549552
continue;
550553
}
551554
}
552-
param.Value = GetValue(columnIndex);
555+
param.Value = row.GetValue(columnIndex);
553556
}
554557

555-
if (await ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false))
558+
if (await resultSet.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false))
556559
throw new InvalidOperationException("Expected only one row.");
557560
}
558561

@@ -565,7 +568,9 @@ private void VerifyNotDisposed()
565568
private ResultSet GetResultSet()
566569
{
567570
VerifyNotDisposed();
568-
return m_resultSet ?? throw new InvalidOperationException("There is no current result set.");
571+
if (m_resultSet is null || m_resultSet.ContainsCommandParameters)
572+
throw new InvalidOperationException("There is no current result set.");
573+
return m_resultSet;
569574
}
570575

571576
readonly CommandBehavior m_behavior;

tests/SideBySide/StoredProcedureTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,29 @@ public async Task StoredProcedureNoResultSet()
130130
Assert.Equal("test value", cmd.Parameters[0].Value);
131131
}
132132

133+
#if !NETCOREAPP1_1_2
134+
[SkippableFact(Baseline = "https://bugs.mysql.com/bug.php?id=97300")]
135+
public async Task GetSchemaTableForNoResultSet()
136+
{
137+
using var cmd = m_database.Connection.CreateCommand();
138+
cmd.CommandText = "out_string";
139+
cmd.CommandType = CommandType.StoredProcedure;
140+
cmd.Parameters.Add(new MySqlParameter
141+
{
142+
ParameterName = "@value",
143+
DbType = DbType.String,
144+
Direction = ParameterDirection.Output,
145+
});
146+
147+
using (var reader = await cmd.ExecuteReaderAsync())
148+
{
149+
Assert.False(await reader.ReadAsync());
150+
Assert.Throws<InvalidOperationException>(() => reader.GetSchemaTable());
151+
Assert.False(await reader.NextResultAsync());
152+
}
153+
}
154+
#endif
155+
133156
[Fact]
134157
public async Task StoredProcedureOutIncorrectType()
135158
{

0 commit comments

Comments
 (0)