Skip to content

Commit f67185f

Browse files
Retrieve's rows can be passed to DequeueAs and DequeueAsMappedDictionaries without a nullable warning (#106)
Retrieve returns QueryResultQueue<object[]>, but DequeueAs<T> and DequeueAsMappedDictionaries took QueryResult<Queue<object?[]>>. QueryResult<T> is invariant, so a nullable-enabled caller got CS8620 on cmd.Retrieve().DequeueAsMappedDictionaries(). The rows hold object values (DBNull for NULL, never null), so the parameters are now QueryResult<Queue<object[]>>, as is Transformer<T>.AsDequeueingEnumerable(QueryResult<Queue<object[]>>). This is a nullable annotation change only; there is no binary change. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 87a5c17 commit f67185f

16 files changed

Lines changed: 111 additions & 54 deletions

‎Benchmarks/Open.Database.Extensions.Benchmarks/SyncBenchmarks.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public long Stream_ODE() => _conn.Command(Sql).Map<Post>().Read(static items =>
9292
public List<Post> QueryResult_Dapper() => _conn.Query<Post>(Sql).AsList();
9393

9494
[BenchmarkCategory("QueryResult"), Benchmark]
95-
public List<Post> QueryResult_ODE() => ((QueryResult<Queue<object?[]>>)(object)_conn.Command(Sql).Retrieve()).DequeueAs<Post>().ToList(); // The same type: Retrieve() annotates its rows object[] and DequeueAs object?[], so the cast goes through object.
95+
public List<Post> QueryResult_ODE() => _conn.Command(Sql).Retrieve().DequeueAs<Post>().ToList();
9696

9797
[BenchmarkCategory("Dictionary"), Benchmark(Baseline = true)]
9898
public Dictionary<int, Post> Dictionary_Dapper() => _conn.Query<Post>(Sql).ToDictionary(p => p.Id);

‎Source/Core/Core/QueryResult.cs‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-

1+
22
namespace Open.Database.Extensions.Core;
33

44
/// <summary>
@@ -184,20 +184,20 @@ public static class QueryResultExtensions
184184
/// .Retrieve method.</param>
185185
/// <returns>An enumerable that dequeues the results and returns a column
186186
/// mapped dictionary for each entry</returns>
187-
public static IEnumerable<Dictionary<string, object?>> DequeueAsMappedDictionaries(this QueryResult<Queue<object?[]>> source)
187+
public static IEnumerable<Dictionary<string, object?>> DequeueAsMappedDictionaries(this QueryResult<Queue<object[]>> source)
188188
{
189189
return source is null
190190
? throw new ArgumentNullException(nameof(source))
191191
: DequeueAsMappedDictionariesCore(source);
192192

193-
static IEnumerable<Dictionary<string, object?>> DequeueAsMappedDictionariesCore(QueryResult<Queue<object?[]>> source)
193+
static IEnumerable<Dictionary<string, object?>> DequeueAsMappedDictionariesCore(QueryResult<Queue<object[]>> source)
194194
{
195-
Queue<object?[]> q = source.Result;
195+
Queue<object[]> q = source.Result;
196196
ImmutableArray<string> names = source.Names;
197197
int count = source.ColumnCount;
198198
while (q.Count != 0)
199199
{
200-
object?[] r = q.Dequeue();
200+
object[] r = q.Dequeue();
201201
var d = new Dictionary<string, object?>(count);
202202
for (int i = 0; i < count; i++)
203203
d.Add(names[i], CoreExtensions.DBNullValueToNull(r[i]));
@@ -207,13 +207,13 @@ public static class QueryResultExtensions
207207
}
208208

209209
/// <inheritdoc
210-
/// cref="DequeueAsMappedDictionaries(QueryResult{Queue{object?[]}})"/>
211-
public static async ValueTask<IEnumerable<Dictionary<string, object?>>> DequeueAsMappedDictionaries(this Task<QueryResult<Queue<object?[]>>> source)
210+
/// cref="DequeueAsMappedDictionaries(QueryResult{Queue{object[]}})"/>
211+
public static async ValueTask<IEnumerable<Dictionary<string, object?>>> DequeueAsMappedDictionaries(this Task<QueryResult<Queue<object[]>>> source)
212212
=> (await (source ?? throw new ArgumentNullException(nameof(source))).ConfigureAwait(false)).DequeueAsMappedDictionaries();
213213

214214
/// <inheritdoc
215-
/// cref="DequeueAsMappedDictionaries(QueryResult{Queue{object?[]}})"/>
216-
public static async ValueTask<IEnumerable<Dictionary<string, object?>>> DequeueAsMappedDictionaries(this ValueTask<QueryResult<Queue<object?[]>>> source)
215+
/// cref="DequeueAsMappedDictionaries(QueryResult{Queue{object[]}})"/>
216+
public static async ValueTask<IEnumerable<Dictionary<string, object?>>> DequeueAsMappedDictionaries(this ValueTask<QueryResult<Queue<object[]>>> source)
217217
=> (await source.ConfigureAwait(false)).DequeueAsMappedDictionaries();
218218

219219
/// <summary>
@@ -229,7 +229,7 @@ public static class QueryResultExtensions
229229
/// of type T.</returns>
230230
/// <seealso cref="RowMap{T}"/>
231231
public static IEnumerable<T> DequeueAs<T>(
232-
this QueryResult<Queue<object?[]>> source,
232+
this QueryResult<Queue<object[]>> source,
233233
RowMap<T>? map = null)
234234
{
235235
if (source is null) throw new ArgumentNullException(nameof(source));
@@ -239,11 +239,11 @@ public static IEnumerable<T> DequeueAs<T>(
239239
return x.AsDequeueingEnumerable(source);
240240
}
241241

242-
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object?[]}}, RowMap{T}?)" />
242+
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object[]}}, RowMap{T}?)" />
243243
/// <remarks>Declare the <see cref="RowMap{T}"/> once and pass it: a map built in the call (a <c>new RowMap&lt;T&gt;(...)</c> or a collection expression in the argument) is rebuilt, and its row reader compiled, on every call.</remarks>
244244
/// <seealso cref="RowMap{T}"/>
245245
public static async ValueTask<IEnumerable<T>> DequeueAs<T>(
246-
this Task<QueryResult<Queue<object?[]>>> source,
246+
this Task<QueryResult<Queue<object[]>>> source,
247247
RowMap<T>? map = null)
248248
{
249249
if (source is null) throw new ArgumentNullException(nameof(source));
@@ -253,27 +253,27 @@ public static async ValueTask<IEnumerable<T>> DequeueAs<T>(
253253
return x.AsDequeueingEnumerable(await source.ConfigureAwait(false));
254254
}
255255

256-
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object?[]}}, RowMap{T}?)" />
256+
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object[]}}, RowMap{T}?)" />
257257
/// <remarks>Declare the <see cref="RowMap{T}"/> once and pass it: a map built in the call (a <c>new RowMap&lt;T&gt;(...)</c> or a collection expression in the argument) is rebuilt, and its row reader compiled, on every call.</remarks>
258258
/// <seealso cref="RowMap{T}"/>
259259
public static async ValueTask<IEnumerable<T>> DequeueAs<T>(
260-
this ValueTask<QueryResult<Queue<object?[]>>> source,
260+
this ValueTask<QueryResult<Queue<object[]>>> source,
261261
RowMap<T>? map = null)
262262
{
263263
var x = new Transformer<T>(map);
264264
return x.AsDequeueingEnumerable(await source.ConfigureAwait(false));
265265
}
266266

267-
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object?[]}}, RowMap{T}?)" />
267+
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object[]}}, RowMap{T}?)" />
268268
[OverloadResolutionPriority(1)]
269269
public static ValueTask<IEnumerable<T>> DequeueAs<T>(
270-
this Task<QueryResult<Queue<object?[]>>> source)
270+
this Task<QueryResult<Queue<object[]>>> source)
271271
=> DequeueAs<T>(source, null);
272272

273-
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object?[]}}, RowMap{T}?)" />
273+
/// <inheritdoc cref="DequeueAs{T}(QueryResult{Queue{object[]}}, RowMap{T}?)" />
274274
[OverloadResolutionPriority(1)]
275275
public static ValueTask<IEnumerable<T>> DequeueAs<T>(
276-
this ValueTask<QueryResult<Queue<object?[]>>> source)
276+
this ValueTask<QueryResult<Queue<object[]>>> source)
277277
=> DequeueAs<T>(source, null);
278278

279279
/// <summary>

‎Source/Core/Core/Transformer.cs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-

1+
22
namespace Open.Database.Extensions.Core;
33

44
/// <summary>
@@ -108,8 +108,8 @@ public void SetNames(ImmutableArray<string> names)
108108
=> Transform = Transformer._plan.ArrayMapper(names);
109109
}
110110

111-
/// <inheritdoc cref="AsDequeueingEnumerable(QueryResult{Queue{object?[]}}, ArrayPool{object?}, bool)" />
112-
public IEnumerable<T> AsDequeueingEnumerable(QueryResult<Queue<object?[]>> results)
111+
/// <inheritdoc cref="AsDequeueingEnumerable(QueryResult{Queue{object[]}}, ArrayPool{object}, bool)" />
112+
public IEnumerable<T> AsDequeueingEnumerable(QueryResult<Queue<object[]>> results)
113113
{
114114
if (results is null) throw new ArgumentNullException(nameof(results));
115115
Contract.EndContractBlock();
@@ -134,7 +134,7 @@ public IEnumerable<T> AsDequeueingEnumerable(QueryResult<Queue<object[]>> result
134134
}
135135

136136
// Each row leaves the queue as it is mapped, so it can be collected while the rest are still pending.
137-
static IEnumerable<T> Drain(Queue<object?[]> queue, Func<object?[], T> map)
137+
static IEnumerable<T> Drain(Queue<object[]> queue, Func<object?[], T> map)
138138
{
139139
while (queue.Count > 0)
140140
yield return map(queue.Dequeue());

‎Source/Core/PublicAPI.Unshipped.txt‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,21 @@ static Open.Database.Extensions.ConnectionExtensions.OpenAsync(this System.Data.
1313
Open.Database.Extensions.MappedQuery<T>.SingleResult() -> Open.Database.Extensions.MappedQuery<T>
1414
Open.Database.Extensions.MappedQueryWithSkip<T>.SingleResult() -> Open.Database.Extensions.MappedQueryWithSkip<T>
1515
Open.Database.Extensions.MappedQueryWithTake<T>.SingleResult() -> Open.Database.Extensions.MappedQueryWithTake<T>
16+
*REMOVED*Open.Database.Extensions.Core.Transformer<T>.AsDequeueingEnumerable(Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>! results) -> System.Collections.Generic.IEnumerable<T>!
17+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>! source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Collections.Generic.IEnumerable<T>!
18+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!>! source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
19+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!>! source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
20+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!> source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
21+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!> source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
22+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>! source) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!
23+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!>! source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!>
24+
*REMOVED*static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object?[]!>!>!> source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!>
25+
Open.Database.Extensions.Core.Transformer<T>.AsDequeueingEnumerable(Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>! results) -> System.Collections.Generic.IEnumerable<T>!
26+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>! source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Collections.Generic.IEnumerable<T>!
27+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!>! source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
28+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!>! source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
29+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!> source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
30+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAs<T>(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!> source, Open.Database.Extensions.RowMap<T>? map = null) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<T>!>
31+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>! source) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!
32+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this System.Threading.Tasks.Task<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!>! source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!>
33+
static Open.Database.Extensions.Core.QueryResultExtensions.DequeueAsMappedDictionaries(this System.Threading.Tasks.ValueTask<Open.Database.Extensions.Core.QueryResult<System.Collections.Generic.Queue<object![]!>!>!> source) -> System.Threading.Tasks.ValueTask<System.Collections.Generic.IEnumerable<System.Collections.Generic.Dictionary<string!, object?>!>!>

‎Tests/Open.Database.Extensions.Core.Tests/CaseInsensitiveMappingTests.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public static class CaseInsensitiveMappingTests
1616
[InlineData("fIrStNaMe")]
1717
public static void Transformer_MatchesColumn_RegardlessOfCase(string columnName)
1818
{
19-
var queue = new Queue<object?[]>();
19+
var queue = new Queue<object[]>();
2020
queue.Enqueue(["Ada"]);
21-
var result = new QueryResult<Queue<object?[]>>([0], [columnName], queue);
21+
var result = new QueryResult<Queue<object[]>>([0], [columnName], queue);
2222

2323
List<TransformerResultsTests.Person> people = [.. result.DequeueAs<TransformerResultsTests.Person>()];
2424

‎Tests/Open.Database.Extensions.Core.Tests/ConstructorBindingTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal static List<T> ReadRows<T>(Route route, ImmutableArray<ColumnDef> colum
2525
{
2626
if (route == Route.Buffered)
2727
{
28-
var result = new QueryResult<Queue<object?[]>>([.. Enumerable.Range(0, columns.Length)], [.. columns.Select(c => c.Name)], new Queue<object?[]>(rows));
28+
var result = new QueryResult<Queue<object[]>>([.. Enumerable.Range(0, columns.Length)], [.. columns.Select(c => c.Name)], new Queue<object[]>(rows!));
2929
return result.DequeueAs(map).ToList();
3030
}
3131

‎Tests/Open.Database.Extensions.Core.Tests/MappingCharacterizationTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ static List<T> Read<T>(bool buffered, ImmutableArray<ColumnDef> columns, object?
2222
return (map is null ? command.Map<T>() : command.Map(map)).ToList();
2323
}
2424

25-
var result = new QueryResult<Queue<object?[]>>([.. Enumerable.Range(0, columns.Length)], [.. columns.Select(c => c.Name)], new Queue<object?[]>([row]));
25+
var result = new QueryResult<Queue<object[]>>([.. Enumerable.Range(0, columns.Length)], [.. columns.Select(c => c.Name)], new Queue<object[]>([row!]));
2626
return result.DequeueAs(map).ToList();
2727
}
2828

‎Tests/Open.Database.Extensions.Core.Tests/MappingCostAndRaceCharacterizationTests.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public static void BufferedRows_MappedByManyThreadsAtOnce_GiveCorrectRows()
272272
ImmutableArray<ColumnDef> columns = [.. names.Select(n => new ColumnDef(n, typeof(object)))];
273273
for (int n = 0; n < 100; n++)
274274
{
275-
var result = new QueryResult<Queue<object?[]>>([.. Enumerable.Range(0, names.Length)], [.. names], new Queue<object?[]>([Row(columns, 1)]));
275+
var result = new QueryResult<Queue<object[]>>([.. Enumerable.Range(0, names.Length)], [.. names], new Queue<object[]>([Row(columns, 1)!]));
276276
AssertGadgets([.. result.DequeueAs(shared)], 1, hasNote: names.Contains("gadget_note"));
277277
}
278278
});
@@ -488,7 +488,7 @@ public static void BufferedRows_OfARecord_MappedByManyThreadsAtOnce_GiveCorrectR
488488
ImmutableArray<ColumnDef> columns = [.. names.Select(n => new ColumnDef(n, typeof(object)))];
489489
for (int n = 0; n < 100; n++)
490490
{
491-
var result = new QueryResult<Queue<object?[]>>([.. Enumerable.Range(0, names.Length)], [.. names], new Queue<object?[]>([Row(columns, 1)]));
491+
var result = new QueryResult<Queue<object[]>>([.. Enumerable.Range(0, names.Length)], [.. names], new Queue<object[]>([Row(columns, 1)!]));
492492
AssertGadgetRecords([.. result.DequeueAs(shared)], 1, hasNote: names.Contains("gadget_note"));
493493
}
494494
});

‎Tests/Open.Database.Extensions.Core.Tests/QueryResultTests.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ namespace Open.Database.Extensions.Tests;
77
[ExcludeFromCodeCoverage]
88
public static class QueryResultTests
99
{
10-
static QueryResult<Queue<object?[]>> Rows(params object?[][] rows)
10+
static QueryResult<Queue<object[]>> Rows(params object?[][] rows)
1111
=> new(
1212
[0, 1],
1313
["Id", "Name"],
14-
new Queue<object?[]>(rows));
14+
new Queue<object[]>(rows!));
1515

1616
[Fact]
1717
public static void Constructor_MismatchedOrdinalAndNameCounts_Throws()
@@ -101,7 +101,7 @@ public static void AsMappedDictionaries_MapsNamesToValues_AndConvertsDBNullToNul
101101
[Fact]
102102
public static void DequeueAsMappedDictionaries_DrainsTheQueueAsItIsEnumerated()
103103
{
104-
QueryResult<Queue<object?[]>> result = Rows(
104+
QueryResult<Queue<object[]>> result = Rows(
105105
[1, "a"],
106106
[2, DBNull.Value],
107107
[3, "c"]);
@@ -120,7 +120,7 @@ public static void DequeueAsMappedDictionaries_DrainsTheQueueAsItIsEnumerated()
120120
[Fact]
121121
public static void DequeueAsMappedDictionaries_NullSource_ThrowsEagerly()
122122
{
123-
QueryResult<Queue<object?[]>> result = null!;
123+
QueryResult<Queue<object[]>> result = null!;
124124

125125
// Not deferred until enumeration.
126126
Assert.Throws<ArgumentNullException>(result.DequeueAsMappedDictionaries);
@@ -129,7 +129,7 @@ public static void DequeueAsMappedDictionaries_NullSource_ThrowsEagerly()
129129
[Fact]
130130
public static async Task DequeueAsMappedDictionaries_Task_AwaitsThenMaps()
131131
{
132-
Task<QueryResult<Queue<object?[]>>> task = Task.FromResult(Rows([1, "a"]));
132+
Task<QueryResult<Queue<object[]>>> task = Task.FromResult(Rows([1, "a"]));
133133

134134
IEnumerable<Dictionary<string, object?>> dictionaries = await task.DequeueAsMappedDictionaries();
135135

0 commit comments

Comments
 (0)