Skip to content

Commit 2732781

Browse files
committed
Merge master into net6.
2 parents 9565d50 + 077acbc commit 2732781

File tree

12 files changed

+157
-46
lines changed

12 files changed

+157
-46
lines changed

NuGet.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
5+
</packageSources>
6+
</configuration>

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ artifacts:
3737
deploy:
3838
- provider: NuGet
3939
api_key:
40-
secure: "NGfY4g2O7NeQY2Mrh28heeTeX4beaJKbNgNVQZ1ydFQVno4x0JAj1Ue3lkbjCK12"
40+
secure: "VEoIuzItFJTuy8bmx7+Gk9duRlwqvtBPbdzYh/5S/pxCLKd8QPYnNw4gvVP0lBOL"
4141
artifact: /.*MySqlConnector[0-9.]+(-(alpha|beta|rc)\.?[0-9.]+)?nupkg/
4242
skip_symbols: true
4343
on:

docs/content/overview/version-history.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ weight: 30
1010

1111
# Version History
1212

13+
### 1.3.2
14+
15+
* Fix a bug that could cause a timed-out query to still throw a `QueryInterrupted` `MySqlException` instead of `CommandTimeoutExpired`.
16+
1317
### 1.3.1
1418

1519
* Remove two new `Info` log messages added in 1.3.0: [#956](https://github.com/mysql-net/MySqlConnector/issues/956).

src/MySqlConnector/Core/ResultSet.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public Row GetCurrentRow()
435435
public ColumnDefinitionPayload[]? ColumnDefinitions { get; private set; }
436436
public MySqlDbType[]? ColumnTypes { get; private set; }
437437
public long LastInsertId { get; private set; }
438-
public int? RecordsAffected { get; private set; }
438+
public ulong? RecordsAffected { get; private set; }
439439
public int WarningCount { get; private set; }
440440
public ResultSetState State { get; private set; }
441441
public bool ContainsCommandParameters { get; private set; }

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,34 @@ public async Task PrepareAsync(IMySqlCommand command, IOBehavior ioBehavior, Can
161161
}
162162

163163
var parameterCount = cachedProcedure.Parameters.Count;
164+
#if NETCOREAPP2_1 || NETCOREAPP3_1 || NETSTANDARD2_1 || NET5_0
165+
commandToPrepare = string.Create(commandText.Length + 7 + parameterCount * 2 + (parameterCount == 0 ? 1 : 0), (commandText, parameterCount), static (buffer, state) =>
166+
{
167+
buffer[0] = 'C';
168+
buffer[1] = 'A';
169+
buffer[2] = 'L';
170+
buffer[3] = 'L';
171+
buffer[4] = ' ';
172+
buffer = buffer.Slice(5);
173+
state.commandText.AsSpan().CopyTo(buffer);
174+
buffer = buffer.Slice(state.commandText.Length);
175+
buffer[0] = '(';
176+
buffer = buffer.Slice(1);
177+
if (state.parameterCount > 0)
178+
{
179+
buffer[0] = '?';
180+
buffer = buffer.Slice(1);
181+
for (var i = 1; i < state.parameterCount; i++)
182+
{
183+
buffer[0] = ',';
184+
buffer[1] = '?';
185+
buffer = buffer.Slice(2);
186+
}
187+
}
188+
buffer[0] = ')';
189+
buffer[1] = ';';
190+
});
191+
#else
164192
var callStatement = new StringBuilder("CALL ", commandText.Length + 8 + parameterCount * 2);
165193
callStatement.Append(commandText);
166194
callStatement.Append('(');
@@ -172,6 +200,7 @@ public async Task PrepareAsync(IMySqlCommand command, IOBehavior ioBehavior, Can
172200
callStatement[callStatement.Length - 1] = ')';
173201
callStatement.Append(';');
174202
commandToPrepare = callStatement.ToString();
203+
#endif
175204
}
176205
else
177206
{

src/MySqlConnector/Core/SqlParser.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,10 @@ public void Parse(string sql)
218218
state = State.Hyphen;
219219
}
220220
else if (ch == '/' && index < sql.Length - 1 && sql[index + 1] == '*')
221+
{
222+
beforeCommentState = state;
221223
state = State.ForwardSlash;
224+
}
222225
else if (ch == '\'')
223226
state = State.SingleQuotedString;
224227
else if (ch == '"')

src/MySqlConnector/MySqlCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationTok
260260

261261
internal async Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
262262
{
263+
Volatile.Write(ref m_commandTimedOut, false);
263264
this.ResetCommandTimeout();
264265
using var registration = ((ICancellableCommand) this).RegisterCancel(cancellationToken);
265266
using var reader = await ExecuteReaderNoResetTimeoutAsync(CommandBehavior.Default, ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -277,6 +278,7 @@ internal async Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, Cancellatio
277278

278279
internal async Task<object?> ExecuteScalarAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
279280
{
281+
Volatile.Write(ref m_commandTimedOut, false);
280282
this.ResetCommandTimeout();
281283
using var registration = ((ICancellableCommand) this).RegisterCancel(cancellationToken);
282284
var hasSetResult = false;
@@ -306,6 +308,7 @@ protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBeha
306308

307309
internal async Task<MySqlDataReader> ExecuteReaderAsync(CommandBehavior behavior, IOBehavior ioBehavior, CancellationToken cancellationToken)
308310
{
311+
Volatile.Write(ref m_commandTimedOut, false);
309312
this.ResetCommandTimeout();
310313
using var registration = ((ICancellableCommand) this).RegisterCancel(cancellationToken);
311314
return await ExecuteReaderNoResetTimeoutAsync(behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -364,8 +367,6 @@ public Task DisposeAsync()
364367

365368
void ICancellableCommand.SetTimeout(int milliseconds)
366369
{
367-
Volatile.Write(ref m_commandTimedOut, false);
368-
369370
if (m_cancelTimerId != 0)
370371
TimerQueue.Instance.Remove(m_cancelTimerId);
371372

src/MySqlConnector/MySqlDataReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public override bool HasRows
211211
}
212212

213213
public override bool IsClosed => Command is null;
214-
public override int RecordsAffected => m_recordsAffected.GetValueOrDefault(-1);
214+
public override int RecordsAffected => m_recordsAffected.HasValue ? checked((int) m_recordsAffected) : -1;
215215

216216
public override int GetOrdinal(string name) => GetResultSet().GetOrdinal(name);
217217

@@ -653,7 +653,7 @@ private ResultSet GetResultSet()
653653
readonly IDictionary<string, CachedProcedure?>? m_cachedProcedures;
654654
CommandListPosition m_commandListPosition;
655655
bool m_closed;
656-
int? m_recordsAffected;
656+
ulong? m_recordsAffected;
657657
bool m_hasWarnings;
658658
ResultSet? m_resultSet;
659659
bool m_hasMoreResults;

src/MySqlConnector/Protocol/Payloads/OkPayload.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace MySqlConnector.Protocol.Payloads
77
{
88
internal sealed class OkPayload
99
{
10-
public int AffectedRowCount { get; }
10+
public ulong AffectedRowCount { get; }
1111
public ulong LastInsertId { get; }
1212
public ServerStatus ServerStatus { get; }
1313
public int WarningCount { get; }
@@ -32,7 +32,7 @@ public static OkPayload Create(ReadOnlySpan<byte> span, bool deprecateEof, bool
3232
var signature = reader.ReadByte();
3333
if (signature != Signature && (!deprecateEof || signature != EofPayload.Signature))
3434
throw new FormatException("Expected to read 0x00 or 0xFE but got 0x{0:X2}".FormatInvariant(signature));
35-
var affectedRowCount = checked((int) reader.ReadLengthEncodedInteger());
35+
var affectedRowCount = reader.ReadLengthEncodedInteger();
3636
var lastInsertId = reader.ReadLengthEncodedInteger();
3737
var serverStatus = (ServerStatus) reader.ReadUInt16();
3838
var warningCount = (int) reader.ReadUInt16();
@@ -96,7 +96,7 @@ public static OkPayload Create(ReadOnlySpan<byte> span, bool deprecateEof, bool
9696
return new OkPayload(affectedRowCount, lastInsertId, serverStatus, warningCount, statusInfo, newSchema);
9797
}
9898

99-
private OkPayload(int affectedRowCount, ulong lastInsertId, ServerStatus serverStatus, int warningCount, string? statusInfo, string? newSchema)
99+
private OkPayload(ulong affectedRowCount, ulong lastInsertId, ServerStatus serverStatus, int warningCount, string? statusInfo, string? newSchema)
100100
{
101101
AffectedRowCount = affectedRowCount;
102102
LastInsertId = lastInsertId;

tests/MySqlConnector.Tests/CancellationTests.cs

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void Execute(int step, int method)
3737
connection.Open();
3838
using var command = connection.CreateCommand();
3939
command.CommandTimeout = 1;
40-
command.CommandText = $"SELECT {4000 + step};";
40+
command.CommandText = $"SELECT 0, 4000, {step}, 0;";
4141
var stopwatch = Stopwatch.StartNew();
4242
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
4343
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
@@ -53,13 +53,60 @@ public void Execute(int step, int method)
5353

5454
[SkipCITheory]
5555
[MemberData(nameof(GetAsyncMethodSteps))]
56-
public async Task ExecuteAsyncs(int step, int method)
56+
public async Task ExecuteAsync(int step, int method)
57+
{
58+
using var connection = new MySqlConnection(m_csb.ConnectionString);
59+
connection.Open();
60+
using var command = connection.CreateCommand();
61+
command.CommandTimeout = 1;
62+
command.CommandText = $"SELECT 0, 4000, {step}, 0;";
63+
var stopwatch = Stopwatch.StartNew();
64+
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
65+
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
66+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
67+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
68+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
69+
70+
// connection should still be usable
71+
Assert.Equal(ConnectionState.Open, connection.State);
72+
command.CommandText = "SELECT 1;";
73+
Assert.Equal(1, command.ExecuteScalar());
74+
}
75+
}
76+
77+
public class CancelBufferedWithCommandTimeout : CancellationTests
78+
{
79+
[SkipCITheory]
80+
[MemberData(nameof(GetSyncMethodSteps))]
81+
public void Execute(int step, int method)
82+
{
83+
using var connection = new MySqlConnection(m_csb.ConnectionString);
84+
connection.Open();
85+
using var command = connection.CreateCommand();
86+
command.CommandTimeout = 1;
87+
command.CommandText = $"SELECT 0, 4000, {step}, 2;";
88+
var stopwatch = Stopwatch.StartNew();
89+
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
90+
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
91+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
92+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
93+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
94+
95+
// connection should still be usable
96+
Assert.Equal(ConnectionState.Open, connection.State);
97+
command.CommandText = "SELECT 1;";
98+
Assert.Equal(1, command.ExecuteScalar());
99+
}
100+
101+
[SkipCITheory]
102+
[MemberData(nameof(GetAsyncMethodSteps))]
103+
public async Task ExecuteAsync(int step, int method)
57104
{
58105
using var connection = new MySqlConnection(m_csb.ConnectionString);
59106
connection.Open();
60107
using var command = connection.CreateCommand();
61108
command.CommandTimeout = 1;
62-
command.CommandText = $"SELECT {4000 + step};";
109+
command.CommandText = $"SELECT 0, 4000, {step}, 2;";
63110
var stopwatch = Stopwatch.StartNew();
64111
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
65112
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
@@ -84,7 +131,7 @@ public void Execute(int step, int method)
84131
connection.Open();
85132
using var command = connection.CreateCommand();
86133
command.CommandTimeout = 10;
87-
command.CommandText = $"SELECT {4000 + step};";
134+
command.CommandText = $"SELECT 0, 4000, {step}, 0;";
88135
var task = Task.Run(async () =>
89136
{
90137
await Task.Delay(TimeSpan.FromSeconds(1));
@@ -111,7 +158,7 @@ public async Task ExecuteAsync(int step, int method)
111158
connection.Open();
112159
using var command = connection.CreateCommand();
113160
command.CommandTimeout = 10;
114-
command.CommandText = $"SELECT {4000 + step};";
161+
command.CommandText = $"SELECT 0, 4000, {step}, 0;";
115162
var task = Task.Run(async () =>
116163
{
117164
await Task.Delay(TimeSpan.FromSeconds(1));
@@ -141,7 +188,7 @@ public async Task Test(int step, int method)
141188
connection.Open();
142189
using var command = connection.CreateCommand();
143190
command.CommandTimeout = 0;
144-
command.CommandText = $"SELECT {4000 + step};";
191+
command.CommandText = $"SELECT 0, 4000, {step}, 0;";
145192
using var source = new CancellationTokenSource(TimeSpan.FromSeconds(1));
146193
var stopwatch = Stopwatch.StartNew();
147194
var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () => await s_executeAsyncMethods[method](command, source.Token));
@@ -166,14 +213,13 @@ public void Test(int step, int method)
166213
connection.Open();
167214
using var command = connection.CreateCommand();
168215
command.CommandTimeout = 1;
169-
var expected = 100 + step;
170-
command.CommandText = $"SELECT {expected};";
216+
command.CommandText = $"SELECT 42, 100, {step}, 0;";
171217
var stopwatch = Stopwatch.StartNew();
172218
var result = s_executeMethods[method](command);
173219
if (method == 1)
174220
Assert.Equal(0, result); // ExecuteNonQuery
175221
else
176-
Assert.Equal(expected, result);
222+
Assert.Equal(42, result);
177223
Assert.InRange(stopwatch.ElapsedMilliseconds, 50, 250);
178224
}
179225
}
@@ -188,15 +234,14 @@ public async Task Test(int step, int method)
188234
connection.Open();
189235
using var command = connection.CreateCommand();
190236
command.CommandTimeout = 0;
191-
var expected = 100 + step;
192-
command.CommandText = $"SELECT {expected};";
237+
command.CommandText = $"SELECT 42, 100, {step}, 0;";
193238
using var source = new CancellationTokenSource(TimeSpan.FromSeconds(1));
194239
var stopwatch = Stopwatch.StartNew();
195240
var result = await s_executeAsyncMethods[method](command, source.Token);
196241
if (method == 1)
197242
Assert.Equal(0, result); // ExecuteNonQuery
198243
else
199-
Assert.Equal(expected, result);
244+
Assert.Equal(42, result);
200245
Assert.InRange(stopwatch.ElapsedMilliseconds, 50, 250);
201246
}
202247
}
@@ -212,7 +257,7 @@ public void Timeout(int method)
212257
connection.Open();
213258
using var command = connection.CreateCommand();
214259
command.CommandTimeout = 1;
215-
command.CommandText = $"SELECT 100;";
260+
command.CommandText = $"SELECT 0, 100, -1, 0;";
216261
var stopwatch = Stopwatch.StartNew();
217262
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
218263
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
@@ -229,10 +274,10 @@ public void NoTimeout(int method)
229274
connection.Open();
230275
using var command = connection.CreateCommand();
231276
command.CommandTimeout = 1;
232-
command.CommandText = $"SELECT 100;";
277+
command.CommandText = $"SELECT 42, 100, -1, 0;";
233278
var stopwatch = Stopwatch.StartNew();
234279
var result = s_executeMethods[method](command);
235-
Assert.Equal(100, result);
280+
Assert.Equal(42, result);
236281
Assert.InRange(stopwatch.ElapsedMilliseconds, 1100, 1500);
237282
}
238283
}
@@ -248,7 +293,7 @@ public async Task Timeout(int method)
248293
connection.Open();
249294
using var command = connection.CreateCommand();
250295
command.CommandTimeout = 1;
251-
command.CommandText = $"SELECT 100;";
296+
command.CommandText = $"SELECT 0, 100, -1, 0;";
252297
var stopwatch = Stopwatch.StartNew();
253298
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
254299
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
@@ -265,10 +310,10 @@ public async Task NoTimeout(int method)
265310
connection.Open();
266311
using var command = connection.CreateCommand();
267312
command.CommandTimeout = 1;
268-
command.CommandText = $"SELECT 100;";
313+
command.CommandText = $"SELECT 42, 100, -1, 0;";
269314
var stopwatch = Stopwatch.StartNew();
270315
var result = await s_executeAsyncMethods[method](command, default);
271-
Assert.Equal(100, result);
316+
Assert.Equal(42, result);
272317
Assert.InRange(stopwatch.ElapsedMilliseconds, 1100, 1500);
273318
}
274319
}
@@ -283,7 +328,7 @@ public void Test(int step, int method)
283328
connection.Open();
284329
using var command = connection.CreateCommand();
285330
command.CommandTimeout = 1;
286-
command.CommandText = $"SELECT {10000 + step};";
331+
command.CommandText = $"SELECT 0, 10000, {step}, 1;";
287332
var stopwatch = Stopwatch.StartNew();
288333
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
289334
Assert.InRange(stopwatch.ElapsedMilliseconds, 2900, 3500);
@@ -305,7 +350,7 @@ public async Task Test(int step, int method)
305350
connection.Open();
306351
using var command = connection.CreateCommand();
307352
command.CommandTimeout = 1;
308-
command.CommandText = $"SELECT {10000 + step};";
353+
command.CommandText = $"SELECT 0, 10000, {step}, 1;";
309354
var stopwatch = Stopwatch.StartNew();
310355
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
311356
Assert.InRange(stopwatch.ElapsedMilliseconds, 2900, 3500);
@@ -328,7 +373,7 @@ public void Execute(int step, int method)
328373
connection.Open();
329374
using var command = connection.CreateCommand();
330375
command.CommandTimeout = 1;
331-
command.CommandText = $"SELECT {10000 + step};";
376+
command.CommandText = $"SELECT 0, 10000, {step}, 1;";
332377
var stopwatch = Stopwatch.StartNew();
333378
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
334379
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
@@ -348,7 +393,7 @@ public async Task ExecuteAsync(int step, int method)
348393
connection.Open();
349394
using var command = connection.CreateCommand();
350395
command.CommandTimeout = 1;
351-
command.CommandText = $"SELECT {10000 + step};";
396+
command.CommandText = $"SELECT 0, 10000, {step}, 1;";
352397
var stopwatch = Stopwatch.StartNew();
353398
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
354399
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);

0 commit comments

Comments
 (0)