Skip to content

Commit 64406ff

Browse files
committed
Add Invoke-Sql cancellation support.
1 parent b5e3443 commit 64406ff

5 files changed

Lines changed: 49 additions & 31 deletions

File tree

PSql.Engine.Tests/Integration/SqlConnectionIntegrationTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ namespace PSql.Integration;
1212
public class SqlConnectionIntegrationTests
1313
{
1414
[Test]
15-
public void ExecuteAndProjectTo_ClrTypes()
15+
public void ExecuteAndProject_ClrTypes()
1616
{
1717
using var connection = new SqlConnection(
1818
IntegrationTestsSetup.Database.ConnectionString,
1919
IntegrationTestsSetup.Credential,
2020
TestSqlLogger.Instance
2121
);
2222

23-
using var result = connection.ExecuteAndProjectTo(
23+
using var result = connection.ExecuteAndProject(
2424
"""
2525
SELECT *, 10 FROM (VALUES (N'a', 1), (N'b', 2)) AS T (S, X);
2626
SELECT *, 20 FROM (VALUES (N'c', 3), (N'd', 4)) AS T (S, Y);
@@ -37,7 +37,7 @@ public void ExecuteAndProjectTo_ClrTypes()
3737

3838
[Test]
3939
[SetCulture("kl-GL")] // Greenlandic
40-
public void ExecuteAndProjectTo_SqlTypes()
40+
public void ExecuteAndProject_SqlTypes()
4141
{
4242
// NOTE: It appears that the current .NET culture is what determines
4343
// the collation of a SqlString. Even a collation specified explicitly
@@ -55,7 +55,7 @@ static SqlString Greenlandic(string s)
5555
TestSqlLogger.Instance
5656
);
5757

58-
using var result = connection.ExecuteAndProjectTo(
58+
using var result = connection.ExecuteAndProject(
5959
"""
6060
SELECT *, 10 FROM (VALUES (N'a', 1), (N'b', 2)) AS T (S, X);
6161
SELECT *, 20 FROM (VALUES
@@ -75,15 +75,15 @@ static SqlString Greenlandic(string s)
7575
}
7676

7777
[Test]
78-
public void ExecuteAndProjectTo_Exception()
78+
public void ExecuteAndProject_Exception()
7979
{
8080
using var connection = new SqlConnection(
8181
IntegrationTestsSetup.Database.ConnectionString,
8282
IntegrationTestsSetup.Credential,
8383
TestSqlLogger.Instance
8484
);
8585

86-
using var result = connection.ExecuteAndProjectTo(
86+
using var result = connection.ExecuteAndProject(
8787
"""
8888
SELECT * FROM (VALUES (1/1)) AS T (X);
8989
SELECT * FROM (VALUES (1/0)) AS T (X);

PSql.Engine.Tests/SqlConnectionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ public class SqlConnectionTests
99
// This test class only backfills coverage gaps in other tests.
1010

1111
[Test]
12-
public void Foo()
12+
public void ExecuteAndProject_NegativeTimeout()
1313
{
1414
var builder = Mock.Of<IObjectBuilder<object>>();
1515

1616
using var connection = new TestSqlConnection();
1717

1818
Should.Throw<ArgumentOutOfRangeException>(() =>
1919
{
20-
connection.ExecuteAndProjectTo("any", builder, timeout: -1);
20+
connection.ExecuteAndProject("any", builder, timeout: -1);
2121
});
2222
}
2323

PSql.Engine/ObjectResultSet.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal sealed class ObjectResultSet<T> : IEnumerator<T>
1212
private readonly SqlDataReader _reader;
1313
private readonly IObjectBuilder<T> _builder;
1414
private readonly bool _useSqlTypes;
15+
private readonly CancellationToken _cancellation;
1516

1617
private T? _current;
1718
private string[]? _columnNames;
@@ -20,16 +21,18 @@ public ObjectResultSet(
2021
SqlConnection connection,
2122
SqlDataReader reader,
2223
IObjectBuilder<T> builder,
23-
bool useSqlTypes)
24+
bool useSqlTypes,
25+
CancellationToken cancellation = default)
2426
{
2527
ArgumentNullException.ThrowIfNull(connection);
2628
ArgumentNullException.ThrowIfNull(reader);
2729
ArgumentNullException.ThrowIfNull(builder);
2830

29-
_connection = connection;
30-
_reader = reader;
31-
_builder = builder;
32-
_useSqlTypes = useSqlTypes;
31+
_connection = connection;
32+
_reader = reader;
33+
_builder = builder;
34+
_useSqlTypes = useSqlTypes;
35+
_cancellation = cancellation;
3336
}
3437

3538
public T Current
@@ -40,9 +43,11 @@ public T Current
4043

4144
public bool MoveNext()
4245
{
43-
while (!_reader.Read())
46+
// Using async from sync to gain cancellation support
47+
48+
while (!_reader.ReadAsync(_cancellation).GetAwaiter().GetResult())
4449
{
45-
if (!_reader.NextResult())
50+
if (!_reader.NextResultAsync(_cancellation).GetAwaiter().GetResult())
4651
return SetNoCurrent();
4752

4853
_columnNames = null;

PSql.Engine/SqlConnection.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,10 @@ protected SqlCommand SetUpCommand(
167167
/// <summary>
168168
/// Ensures that the connection is open.
169169
/// </summary>
170-
protected void AutoOpen()
170+
protected void AutoOpen(CancellationToken cancellation)
171171
{
172172
if (Connection.State == ConnectionState.Closed)
173-
Connection.Open();
173+
Connection.OpenAsync(cancellation).GetAwaiter().GetResult();
174174
}
175175

176176
/// <summary>
@@ -193,6 +193,9 @@ protected void AutoOpen()
193193
/// the <see cref="System.Data.SqlTypes"/> namespace, such as
194194
/// <see cref="System.Data.SqlTypes.SqlInt32"/>.
195195
/// </param>
196+
/// <param name="cancellation">
197+
/// A token to monitor for cancellation requests.
198+
/// </param>
196199
/// <returns>
197200
/// A sequence of objects created by executing the <paramref name="sql"/>
198201
/// batch and projecting each result row to an object using the
@@ -221,18 +224,21 @@ protected void AutoOpen()
221224
/// <exception cref="ObjectDisposedException">
222225
/// Thrown by the underlying ADO.NET connection or command objects.
223226
/// </exception>
224-
public IEnumerator<T> ExecuteAndProjectTo<T>(
227+
public IEnumerator<T> ExecuteAndProject<T>(
225228
string sql,
226229
IObjectBuilder<T> builder,
227-
int timeout = 0,
228-
bool useSqlTypes = false)
230+
int timeout = 0,
231+
bool useSqlTypes = false,
232+
CancellationToken cancellation = default)
229233
{
234+
// Using async from sync to gain cancellation support
235+
230236
SetUpCommand(sql, timeout);
231-
AutoOpen();
237+
AutoOpen(cancellation);
232238

233-
var reader = Command.ExecuteReader();
239+
var reader = Command.ExecuteReaderAsync(cancellation).GetAwaiter().GetResult();
234240

235-
return new ObjectResultSet<T>(this, reader, builder, useSqlTypes);
241+
return new ObjectResultSet<T>(this, reader, builder, useSqlTypes, cancellation);
236242
}
237243

238244
/// <summary>

PSql/Commands/InvokeSqlCommand.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public class InvokeSqlCommand : ConnectedCmdlet
5757
[Parameter]
5858
public TimeSpan? Timeout { get; set; }
5959

60-
private readonly E.SqlCmdPreprocessor _preprocessor;
60+
private readonly E.SqlCmdPreprocessor _preprocessor;
61+
private readonly CancellationTokenSource _cancellation;
6162

6263
private bool ShouldUsePreprocessing
6364
=> !NoPreprocessing;
@@ -68,6 +69,7 @@ private bool ShouldUseErrorHandling
6869
public InvokeSqlCommand()
6970
{
7071
_preprocessor = new();
72+
_cancellation = new();
7173
}
7274

7375
protected override void ProcessRecord()
@@ -90,6 +92,11 @@ protected override void ProcessRecord()
9092
Execute(scripts);
9193
}
9294

95+
protected override void StopProcessing()
96+
{
97+
_cancellation.Cancel();
98+
}
99+
93100
private static IEnumerable<string> ExcludeNullOrEmpty(IEnumerable<string?> scripts)
94101
{
95102
return scripts.Where(s => s.HasContent())!;
@@ -124,16 +131,16 @@ private IEnumerator<object> ExecuteAndProjectToObjects(string batch)
124131
? (int) Timeout.Value.TotalSeconds
125132
: DefaultTimeoutSeconds;
126133

127-
return Connection.InnerConnection.ExecuteAndProjectTo(
128-
batch, new PSObjectBuilder(), timeout, UseSqlTypes
134+
return Connection.InnerConnection.ExecuteAndProject(
135+
batch, new PSObjectBuilder(), timeout, UseSqlTypes, _cancellation.Token
129136
);
130137
}
131138

132-
#if DECIDE_ON_CANCELLATION
133-
protected override void StopProcessing()
139+
/// <inheritdoc/>
140+
public override void Dispose()
134141
{
135-
// TODO: Figure out how to cancel
136-
// Async from sync, perhaps?
142+
_cancellation.Dispose();
143+
144+
base.Dispose();
137145
}
138-
#endif
139146
}

0 commit comments

Comments
 (0)