Skip to content

Commit f97f8ef

Browse files
committed
Add nullable annotations to MySqlParameter and CommandExecutor.
Fix several NullReferenceExceptions in MySqlParameter and MySqlParameterCollection.
1 parent 7293e2f commit f97f8ef

11 files changed

+104
-93
lines changed

src/MySqlConnector/Core/BatchedCommandPayloadCreator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System;
32
using System.Buffers.Binary;
43
using System.Collections.Generic;
@@ -11,7 +10,7 @@ internal sealed class BatchedCommandPayloadCreator : ICommandPayloadCreator
1110
{
1211
public static ICommandPayloadCreator Instance { get; } = new BatchedCommandPayloadCreator();
1312

14-
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer)
13+
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer)
1514
{
1615
writer.Write((byte) CommandKind.Multi);
1716
bool? firstResult = default;

src/MySqlConnector/Core/CommandExecutor.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System;
32
using System.Collections.Generic;
43
using System.Data;
@@ -21,40 +20,46 @@ public static async Task<DbDataReader> ExecuteReaderAsync(IReadOnlyList<IMySqlCo
2120
cancellationToken.ThrowIfCancellationRequested();
2221
var commandListPosition = new CommandListPosition(commands);
2322
var command = commands[0];
23+
24+
// pre-requisite: Connection is non-null must be checked before calling this method
25+
var connection = command.Connection!;
26+
2427
if (Log.IsDebugEnabled())
25-
Log.Debug("Session{0} ExecuteReader {1} CommandCount: {2}", command.Connection.Session.Id, ioBehavior, commands.Count);
28+
Log.Debug("Session{0} ExecuteReader {1} CommandCount: {2}", connection.Session.Id, ioBehavior, commands.Count);
2629

27-
Dictionary<string, CachedProcedure> cachedProcedures = null;
30+
Dictionary<string, CachedProcedure?>? cachedProcedures = null;
2831
foreach (var command2 in commands)
2932
{
3033
if (command2.CommandType == CommandType.StoredProcedure)
3134
{
3235
if (cachedProcedures is null)
33-
cachedProcedures = new Dictionary<string, CachedProcedure>();
34-
if (!cachedProcedures.ContainsKey(command2.CommandText))
35-
cachedProcedures.Add(command2.CommandText, await command2.Connection.GetCachedProcedure(ioBehavior, command2.CommandText, cancellationToken).ConfigureAwait(false));
36+
cachedProcedures = new Dictionary<string, CachedProcedure?>();
37+
var commandText = command2.CommandText!;
38+
if (!cachedProcedures.ContainsKey(commandText))
39+
cachedProcedures.Add(commandText, await connection.GetCachedProcedure(ioBehavior, commandText, cancellationToken).ConfigureAwait(false));
3640
}
3741
}
3842

3943
var writer = new ByteBufferWriter();
40-
if (!payloadCreator.WriteQueryCommand(ref commandListPosition, cachedProcedures, writer))
44+
// cachedProcedures will be non-null if there is a stored procedure, which is also the only time it will be read
45+
if (!payloadCreator.WriteQueryCommand(ref commandListPosition, cachedProcedures!, writer))
4146
throw new InvalidOperationException("ICommandPayloadCreator failed to write query payload");
4247

4348
cancellationToken.ThrowIfCancellationRequested();
4449

4550
using (var payload = writer.ToPayloadData())
4651
using (command.CancellableCommand.RegisterCancel(cancellationToken))
4752
{
48-
command.Connection.Session.StartQuerying(command.CancellableCommand);
53+
connection.Session.StartQuerying(command.CancellableCommand);
4954
command.SetLastInsertedId(-1);
5055
try
5156
{
52-
await command.Connection.Session.SendAsync(payload, ioBehavior, CancellationToken.None).ConfigureAwait(false);
57+
await connection.Session.SendAsync(payload, ioBehavior, CancellationToken.None).ConfigureAwait(false);
5358
return await MySqlDataReader.CreateAsync(commandListPosition, payloadCreator, cachedProcedures, command, behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
5459
}
5560
catch (MySqlException ex) when (ex.Number == (int) MySqlErrorCode.QueryInterrupted && cancellationToken.IsCancellationRequested)
5661
{
57-
Log.Warn("Session{0} query was interrupted", command.Connection.Session.Id);
62+
Log.Warn("Session{0} query was interrupted", connection.Session.Id);
5863
throw new OperationCanceledException(cancellationToken);
5964
}
6065
catch (Exception ex) when (payload.Span.Length > 4_194_304 && (ex is SocketException || ex is IOException || ex is MySqlProtocolException))

src/MySqlConnector/Core/ConcatenatedCommandPayloadCreator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System.Collections.Generic;
32
using MySqlConnector.Logging;
43
using MySqlConnector.Protocol;
@@ -10,7 +9,7 @@ internal sealed class ConcatenatedCommandPayloadCreator : ICommandPayloadCreator
109
{
1110
public static ICommandPayloadCreator Instance { get; } = new ConcatenatedCommandPayloadCreator();
1211

13-
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer)
12+
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer)
1413
{
1514
if (commandListPosition.CommandIndex == commandListPosition.Commands.Count)
1615
return false;
@@ -21,7 +20,7 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
2120
{
2221
var command = commandListPosition.Commands[commandListPosition.CommandIndex];
2322
if (Log.IsDebugEnabled())
24-
Log.Debug("Session{0} Preparing command payload; CommandText: {1}", command.Connection.Session.Id, command.CommandText);
23+
Log.Debug("Session{0} Preparing command payload; CommandText: {1}", command.Connection!.Session.Id, command.CommandText);
2524

2625
isComplete = SingleCommandPayloadCreator.WriteQueryPayload(command, cachedProcedures, writer);
2726
commandListPosition.CommandIndex++;

src/MySqlConnector/Core/ICommandPayloadCreator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System.Collections.Generic;
32
using MySqlConnector.Protocol.Serialization;
43

@@ -16,6 +15,6 @@ internal interface ICommandPayloadCreator
1615
/// <param name="cachedProcedures">A <see cref="CachedProcedure"/> for all the stored procedures in the command list, if any.</param>
1716
/// <param name="writer">The <see cref="ByteBufferWriter"/> to write the payload to.</param>
1817
/// <returns><c>true</c> if a command was written; otherwise, <c>false</c> (if there were no more commands in the list).</returns>
19-
bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer);
18+
bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer);
2019
}
2120
}

src/MySqlConnector/Core/IMySqlCommand.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System;
32
using System.Data;
43
using MySql.Data.MySqlClient;
@@ -10,23 +9,23 @@ namespace MySqlConnector.Core
109
/// </summary>
1110
internal interface IMySqlCommand
1211
{
13-
string CommandText { get; }
12+
string? CommandText { get; }
1413
CommandType CommandType { get; }
15-
MySqlParameterCollection RawParameters { get; }
16-
PreparedStatements TryGetPreparedStatements();
17-
MySqlConnection Connection { get; }
14+
MySqlParameterCollection? RawParameters { get; }
15+
PreparedStatements? TryGetPreparedStatements();
16+
MySqlConnection? Connection { get; }
1817
long LastInsertedId { get; }
1918
void SetLastInsertedId(long lastInsertedId);
20-
MySqlParameterCollection OutParameters { get; set; }
21-
MySqlParameter ReturnParameter { get; set; }
19+
MySqlParameterCollection? OutParameters { get; set; }
20+
MySqlParameter? ReturnParameter { get; set; }
2221
ICancellableCommand CancellableCommand { get; }
2322
}
2423

2524
internal static class IMySqlCommandExtensions
2625
{
2726
public static StatementPreparerOptions CreateStatementPreparerOptions(this IMySqlCommand command)
2827
{
29-
var connection = command.Connection;
28+
var connection = command.Connection!;
3029
var statementPreparerOptions = StatementPreparerOptions.None;
3130
if (connection.AllowUserVariables || command.CommandType == CommandType.StoredProcedure)
3231
statementPreparerOptions |= StatementPreparerOptions.AllowUserVariables;

src/MySqlConnector/Core/SingleCommandPayloadCreator.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System;
32
using System.Collections.Generic;
43
using System.Data;
@@ -18,7 +17,7 @@ internal sealed class SingleCommandPayloadCreator : ICommandPayloadCreator
1817
// with this as the first column name, the result set will be treated as 'out' parameters for the previous command.
1918
public static string OutParameterSentinelColumnName => "\uE001\b\x0B";
2019

21-
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer)
20+
public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer)
2221
{
2322
if (commandListPosition.CommandIndex == commandListPosition.Commands.Count)
2423
return false;
@@ -28,7 +27,7 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
2827
if (preparedStatements is null)
2928
{
3029
if (Log.IsDebugEnabled())
31-
Log.Debug("Session{0} Preparing command payload; CommandText: {1}", command.Connection.Session.Id, command.CommandText);
30+
Log.Debug("Session{0} Preparing command payload; CommandText: {1}", command.Connection!.Session.Id, command.CommandText);
3231

3332
writer.Write((byte) CommandKind.Query);
3433
WriteQueryPayload(command, cachedProcedures, writer);
@@ -57,15 +56,15 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
5756
/// <param name="cachedProcedures">The cached procedures.</param>
5857
/// <param name="writer">The output writer.</param>
5958
/// <returns><c>true</c> if a complete command was written; otherwise, <c>false</c>.</returns>
60-
public static bool WriteQueryPayload(IMySqlCommand command, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer) =>
59+
public static bool WriteQueryPayload(IMySqlCommand command, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer) =>
6160
(command.CommandType == CommandType.StoredProcedure) ? WriteStoredProcedure(command, cachedProcedures, writer) : WriteCommand(command, writer);
6261

6362
private static void WritePreparedStatement(IMySqlCommand command, PreparedStatement preparedStatement, ByteBufferWriter writer)
6463
{
6564
var parameterCollection = command.RawParameters;
6665

6766
if (Log.IsDebugEnabled())
68-
Log.Debug("Session{0} Preparing command payload; CommandId: {1}; CommandText: {2}", command.Connection.Session.Id, preparedStatement.StatementId, command.CommandText);
67+
Log.Debug("Session{0} Preparing command payload; CommandId: {1}; CommandText: {2}", command.Connection!.Session.Id, preparedStatement.StatementId, command.CommandText);
6968

7069
writer.Write(preparedStatement.StatementId);
7170
writer.Write((byte) 0);
@@ -84,7 +83,7 @@ private static void WritePreparedStatement(IMySqlCommand command, PreparedStatem
8483
throw new MySqlException("Parameter '{0}' must be defined.".FormatInvariant(parameterName));
8584
else if (parameterIndex < 0 || parameterIndex >= (parameterCollection?.Count ?? 0))
8685
throw new MySqlException("Parameter index {0} is invalid when only {1} parameter{2} defined.".FormatInvariant(parameterIndex, parameterCollection?.Count ?? 0, parameterCollection?.Count == 1 ? " is" : "s are"));
87-
parameters[i] = parameterCollection[parameterIndex];
86+
parameters[i] = parameterCollection![parameterIndex];
8887
}
8988

9089
// write null bitmap
@@ -118,7 +117,7 @@ private static void WritePreparedStatement(IMySqlCommand command, PreparedStatem
118117
mySqlDbType = TypeMapper.Instance.GetMySqlDbTypeForDbType(dbType);
119118
}
120119

121-
writer.Write(TypeMapper.ConvertToColumnTypeAndFlags(mySqlDbType, command.Connection.GuidFormat));
120+
writer.Write(TypeMapper.ConvertToColumnTypeAndFlags(mySqlDbType, command.Connection!.GuidFormat));
122121
}
123122

124123
var options = command.CreateStatementPreparerOptions();
@@ -127,22 +126,22 @@ private static void WritePreparedStatement(IMySqlCommand command, PreparedStatem
127126
}
128127
}
129128

130-
private static bool WriteStoredProcedure(IMySqlCommand command, IDictionary<string, CachedProcedure> cachedProcedures, ByteBufferWriter writer)
129+
private static bool WriteStoredProcedure(IMySqlCommand command, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer)
131130
{
132131
var parameterCollection = command.RawParameters;
133-
var cachedProcedure = cachedProcedures[command.CommandText];
132+
var cachedProcedure = cachedProcedures[command.CommandText!];
134133
if (cachedProcedure is object)
135134
parameterCollection = cachedProcedure.AlignParamsWithDb(parameterCollection);
136135

137-
MySqlParameter returnParameter = null;
136+
MySqlParameter? returnParameter = null;
138137
var outParameters = new MySqlParameterCollection();
139138
var outParameterNames = new List<string>();
140139
var inParameters = new MySqlParameterCollection();
141140
var argParameterNames = new List<string>();
142141
var inOutSetParameters = "";
143142
for (var i = 0; i < (parameterCollection?.Count ?? 0); i++)
144143
{
145-
var param = parameterCollection[i];
144+
var param = parameterCollection![i];
146145
var inName = "@inParam" + i;
147146
var outName = "@outParam" + i;
148147
switch (param.Direction)

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System.Data;
32
using MySqlConnector.Core;
43

@@ -11,13 +10,13 @@ public MySqlBatchCommand()
1110
{
1211
}
1312

14-
public MySqlBatchCommand(string commandText)
13+
public MySqlBatchCommand(string? commandText)
1514
{
1615
CommandText = commandText;
1716
CommandType = CommandType.Text;
1817
}
1918

20-
public string CommandText { get; set; }
19+
public string? CommandText { get; set; }
2120
public CommandType CommandType { get; set; }
2221
public CommandBehavior CommandBehavior { get; set; }
2322
public int RecordsAffected { get; set; }
@@ -32,25 +31,25 @@ public MySqlParameterCollection Parameters
3231
}
3332
}
3433

35-
MySqlParameterCollection IMySqlCommand.RawParameters => m_parameterCollection;
34+
MySqlParameterCollection? IMySqlCommand.RawParameters => m_parameterCollection;
3635

37-
MySqlConnection IMySqlCommand.Connection => Batch.Connection;
36+
MySqlConnection? IMySqlCommand.Connection => Batch?.Connection;
3837

3938
long IMySqlCommand.LastInsertedId => m_lastInsertedId;
4039

41-
PreparedStatements IMySqlCommand.TryGetPreparedStatements() => null;
40+
PreparedStatements? IMySqlCommand.TryGetPreparedStatements() => null;
4241

4342
void IMySqlCommand.SetLastInsertedId(long lastInsertedId) => m_lastInsertedId = lastInsertedId;
4443

45-
MySqlParameterCollection IMySqlCommand.OutParameters { get; set; }
44+
MySqlParameterCollection? IMySqlCommand.OutParameters { get; set; }
4645

47-
MySqlParameter IMySqlCommand.ReturnParameter { get; set; }
46+
MySqlParameter? IMySqlCommand.ReturnParameter { get; set; }
4847

49-
ICancellableCommand IMySqlCommand.CancellableCommand => Batch;
48+
ICancellableCommand IMySqlCommand.CancellableCommand => Batch!;
5049

51-
internal MySqlBatch Batch { get; set; }
50+
internal MySqlBatch? Batch { get; set; }
5251

53-
MySqlParameterCollection m_parameterCollection;
52+
MySqlParameterCollection? m_parameterCollection;
5453
long m_lastInsertedId;
5554
}
5655
}

0 commit comments

Comments
 (0)