M5: query shapes (single, optional, scalar, execute, streaming)#5
Merged
Conversation
Incrementing the preview suffix per milestone keeps packages built from different milestones of the same phase distinguishable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The model gains a ResultShape discriminator and records the declared return type verbatim, so the emitter dispatches per shape instead of assuming a row list. Task<T> reads with strict-single semantics (zero or multiple rows throw, surfacing missing-WHERE bugs), Task<T?> returns null on zero rows and still throws on more than one. Optionality comes from Nullable<T> for value-type rows and the nullable annotation for reference-type rows. The M4 row-list golden stays byte-identical through the refactor; a new golden locks the single-row output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Element resolution is now orthogonal to the outer shape: a supported scalar type reads the first column directly (ordinal 0, no GetOrdinal), anything else stays a constructor-mapped row. Non-nullable scalars throw on no rows or DBNull, matching ExecuteScalar expectations without the strict-single check row shapes get, since scalar queries are typically aggregates; nullable scalars return null in both cases. Scalar lists fall out of the same resolution, including nullable elements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task<int> cannot mean both a scalar SELECT and rows affected, so non-query statements get their own attribute instead of overloading [SqlQuery]. The name avoids [SqlCommand], which would hit CS1614 ambiguity against Microsoft.Data.SqlClient.SqlCommand in consumer code. [SqlExecute] methods return Task<int> (rows affected) or Task (discarded), sharing the parser, parameter binding, and transaction plumbing; SQLB009 rejects other return types and SQLB010 rejects carrying both attributes, reported exactly once even though both pipelines see the method. Usage diagnostics now name the offending attribute in their messages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IAsyncEnumerable<T> methods emit as async iterators that yield each row (or first-column scalar) as it is read, so consumers can process large result sets without buffering; command and reader disposal ride the iterator finally blocks. The generated implementation part decorates the CancellationToken parameter with [EnumeratorCancellation] so WithCancellation flows the token; the compiler merges the attribute across partial parts, which the warning-free compile tests confirm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Row types without a parameterized constructor can now map columns through public settable (set/init) properties, including inherited ones, on a parameterless-constructible type, emitted as an object initializer. A single parameterized public constructor still takes precedence, so positional records keep constructor mapping. All settable properties must be supported column types: silently skipping an unmappable property would hide bugs, so SQLB005 rejects it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Integration tests exercise the full M5 matrix against a real in-memory SQLite database: strict single row (hit and miss), optional row miss, scalar count, scalar list, await-foreach streaming, [SqlExecute] rows affected, and property-mapped mutable rows including the NULL-to-null path. The streaming method also validates in a real consumer-shaped build (TreatWarningsAsErrors) that [EnumeratorCancellation] emitted on the implementation part merges cleanly across partial declarations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers M5 at
0.2.0-preview.2: the full return-shape matrix for generated queries, completing the codegen surface ahead of M6 (AOT + benchmarks).Task<T>with strict-single semantics (zero or multiple rows throw, surfacing missing-WHERE bugs);Task<T?>returnsnullon zero rows and still throws on more than one. Optionality comes fromNullable<T>for value-type rows and NRT annotations for reference rows.Task<T>,Task<T?>, andTask<IReadOnlyList<T>>, so scalar lists (including nullable elements) fall out for free. Non-nullable scalars matchExecuteScalarexpectations (first row, throw on no rows/DBNull).[SqlExecute]: non-query statements get their own attribute sinceTask<int>cannot mean both a scalar SELECT and rows affected; named to avoid CS1614 ambiguity withMicrosoft.Data.SqlClient.SqlCommand. ReturnsTask<int>(rows affected) orTask. New diagnostics:SQLB009(unsupported execute return type),SQLB010(both attributes on one method, reported exactly once).IAsyncEnumerable<T>methods emit as async iterators yielding rows as read — no buffering;[EnumeratorCancellation]emitted on the implementation-part parameter merges cleanly across partial declarations (no CS8425), confirmed by warning-free compile tests and theTreatWarningsAsErrorsintegration build.set/initproperties (inherited included) materialize via object initializers; a single parameterized public constructor still takes precedence, so positional records keep constructor mapping. Unmappable settable properties are an SQLB005 error rather than being silently skipped.Test plan
await foreachstreaming, rows affected, and property-mapped mutable rows including the NULL→null path.dotnet format --verify-no-changes, zero-warning build, anddotnet packverified locally.🤖 Generated with Claude Code