Release v0.5.0 — source-generated AOT accessors + CI hardening - #221
Conversation
Enables the SDK's package validation (Microsoft.DotNet.ApiCompat, integrated) so `dotnet pack` compares the public surface against the last published release and fails on a binary/source break a non-MAJOR bump forbids. - EnablePackageValidation=true, PackageValidationBaselineVersion=0.4.0. - Runs in release.yaml's existing Pack job — no new hand-rolled download/ApiCompat step needed (the SDK downloads the baseline package itself). - Intentional MAJOR-release breaks recorded via -p:GenerateCompatibilitySuppressionFile=true (writes CompatibilitySuppressions.xml). Verified: clean `dotnet pack` downloads the 0.4.0 baseline and passes (0 CP breaks; vNext has no API change yet). Baseline must track the last PUBLISHED release — bump it right after each release cut. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the canonical SECURITY.md section (structure from Etl-DbClient #240 / ETL-FixedWidth) with this repo's specifics: OIDC Trusted Publishing release path, no fallback (OIDC identity Chris-Wolfgang/ETL-SqlBulkCopy), owner, leaf- library downstream scope, and package coordinates for unlisting. No placeholders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tests/Wolfgang.Etl.SqlBulkCopy.Tests.DocExamples (net8.0) — a Roslyn-based guard against XML-doc example rot. It walks up from the test binary directory to locate src/ (avoids [CallerFilePath], which resolves to a deterministic '/_/' path under CI), extracts every <example><code> block, un-escapes the XML entities, wraps each in a synthetic harness (a Person record + an open connection + an async source + a token), and compiles it with Microsoft.CodeAnalysis.CSharp. A snippet that no longer compiles fails the test. Single-TFM project on purpose — sidesteps the main suite's 13-TFM matrix (Roslyn doesn't need net462). Runs in CI via the solution test on net8.0. Extend the harness as new snippets introduce new contextual symbols. Verified: passes on the current example; a negative test (injecting a call to a non-existent method) correctly fails with 'does not compile' (CS1061). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…evalidation build: API/ABI compatibility gate via PackageValidation (#88)
…-path docs(security): Release path & compromise scope appendix (#103)
test: compile every XML-doc <example> snippet (#93)
Records the 0.5.0+ decision to move property-getter emission from runtime Expression.Compile to a compile-time Roslyn source generator: keeps the compiled-getter throughput while making the hot path Native-AOT-clean. Generated metadata is the preferred provider, reflection remains the runtime fallback, one package, rule contract single-sourced via conformance tests. Corrects ADR 0004's inaccurate 'throws under AOT' claim (Expression.Compile falls back to the interpreter). Tracked by #95. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(adr): 0006 source-generated property accessors (AOT)
Adds the runtime seam the source-generator pilot plugs into: a GeneratedAccessorRegistry that generated code registers compile-time getters into, and ColumnMap now prefers a registered generated getter over the runtime Expression.Compile'd one (falling back to it when none is registered). No generator yet and no packaging change — this is the preferred-provider / reflection-fallback seam from ADR 0006, unit-tested by hand-registering a sentinel getter. The Register entry point is public (generated code runs in the consumer assembly, no InternalsVisibleTo) and marked [EditorBrowsable(Never)] as infra. Refs #95. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…seam feat: source-generated accessor seam (ADR 0006, #95, PR-A)
Adds the incremental source generator that fills the PR-A seam. A type marked [BulkCopyable] gets strongly-typed getters emitted at compile time and registered with GeneratedAccessorRegistry from a module initializer, so its bulk-copy hot path reads values with no runtime Expression.Compile — the Native-AOT-clean path from ADR 0006. Reflection stays the fallback for every un-marked or compile-time-invisible type. - New src/Wolfgang.Etl.SqlBulkCopy.SourceGenerator (netstandard2.0, Roslyn 4.11.0), packed as an analyzer asset INTO the existing nupkg (analyzers/dotnet/cs) — one package, no second NuGet. - Public [BulkCopyable] opt-in attribute. - Registration is emitted under #if NET5_0_OR_GREATER (module initializers); legacy TFMs compile it away and fall back to reflection — no polyfill, no collision. - End-to-end proof: the generator is referenced as an analyzer by Tests.Unit; a [BulkCopyable] fixture's accessors are asserted registered + correct (net5.0+). net462 builds clean (guard compiles to nothing); pack verified to contain exactly one analyzer dll. Known follow-up (PR-C): the enum-underlying converter is a second Expression.Compile site not yet source-generated; conformance test + PublishAot smoke also land in PR-C. Refs #95. Docs: ADR 0006. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, PR-C) Closes the second Expression.Compile site. The enum->underlying converter (ColumnMap._enumConverter) now prefers a source-generated converter registered via GeneratedAccessorRegistry.RegisterEnumConverter, falling back to ReflectionHelpers.CompileEnumToUnderlyingConverter. The generator emits a ConvertEnum_<T>(object)=>(object)(TUnderlying)(TEnum)boxed per distinct enum (incl. nullable-enum) column on a [BulkCopyable] type. With this, a marked type with enum columns has NO runtime Expression.Compile on its hot path. Adds the ADR 0006 single-sourcing guard: a conformance test asserting the generated getters AND enum converters produce identical results to the reflection path across a corpus of [BulkCopyable] fixtures (int/string/enum/ byte-enum/nullable-enum) — so the generated fast path can't silently diverge from the reflection fallback. 268 unit tests green (net10.0); net462 builds clean (conformance is net5.0+). Refs #95. Docs: ADR 0006. PublishAot smoke follows as PR-D. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a console that publishes with <PublishAot>true</PublishAot> and reads values through a [BulkCopyable] type's source-generated getters and enum converter, plus a Linux CI job that publishes it natively and runs it. The smoke reaches only TypeMap/ColumnMap (via InternalsVisibleTo), never SqlBulkCopyLoader, so Microsoft.Data.SqlClient is trimmed out of the reachable graph — keeping the AOT signal about THIS library's code, not SqlClient's separate AOT story (ADR 0006). The CI job asserts the native publish emits no IL2xxx/IL3xxx referencing Wolfgang.Etl.SqlBulkCopy and that the binary runs (exit 0). Verified locally: JIT run prints ok=True (exit 0); AOT publish reaches ILC with zero IL warnings (SqlClient trimmed) — only the native linker is unavailable on this Windows box (no C++ Desktop workload), which is why the native compile+run runs on Linux CI. Refs #95. Docs: ADR 0006. Completes the 3-part pilot (seam/generator/enum) with the empirical AOT proof. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a 'Native AOT & trimming' section to the README (what [BulkCopyable] does, opt-in/additive, one-package, net5.0+ caveat, and the SqlClient external-gate note) plus a Features-table row, and a [Unreleased] CHANGELOG entry for the new public surface (BulkCopyableAttribute + GeneratedAccessorRegistry). Refs #95. Docs: ADR 0006. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SourceLink workflow built every src/**/*.csproj with -f net10.0, which NETSDK1005-fails on the netstandard2.0-only source generator (Roslyn analyzers can't target net10.0). Skip projects whose csproj has no net10.0 target — they produce no net10.0 PDB to verify anyway. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: source generator for compile-time accessors (ADR 0006, #95, PR-B)
…rmance feat: source-generate enum converters + conformance test (ADR 0006, #95, PR-C)
test: Native AOT smoke consumer + CI (ADR 0006, #95, PR-D)
docs: document [BulkCopyable] Native AOT support (ADR 0006, #95)
Previously the Native AOT smoke only ran on PRs targeting main/vNext, so source-generator work landing directly on vNext wasn't natively verified until a release PR. Add a push trigger (same path filter) so vNext pushes exercise the native publish+run. Opening this PR also fires the first real native run, verifying the merged pilot end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: run AOT smoke on pushes to vNext (+ first native verification)
… (ADR 0006, #95) Option B for the AOT-mapping gap the smoke surfaced (IL2070 in TypeMap's reflection). The generator now emits, per flat [BulkCopyable] type, a full GeneratedTypeDescriptor (schema/table/column metadata) mirroring TypeMap's rules; TypeMap.Create prefers it and builds the map reflection-free (delegates from the PR-B/C accessor registry). The reflection path is quarantined behind [RequiresUnreferencedCode] (+ netFx/netstandard polyfills) with a justified suppression on Create, so a [BulkCopyable] type's mapping no longer reflects over the type — clearing IL2070 under Native AOT. Guarded by a descriptor conformance test: the generated map equals the reflection map of a structurally-identical plain type across flat / enum / [Table]+[Column] / per-load-override cases. 273 unit tests green; all 5 TFMs build clean. Types with nested-table properties still use the reflection path (recursive nested descriptors are the next increment). Refs #95. Docs: ADR 0006. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ement 2) Closes the nested-table gap. The generator now emits a GeneratedNestedTableDescriptor per nested collection and includes it in the type descriptor — but only when the WHOLE graph is [BulkCopyable] (recursive IsFullyGeneratable eligibility check; cyclic or partly-unmarked graphs fall back to reflection, keeping AOT honest). TypeMap.BuildFromDescriptor resolves each child via Create (its own descriptor), and NestedTableMap gained a reflection-free ctor pulling the collection getter from the accessor registry — so a fully-marked graph maps with zero reflection. Conformance extended: generated nested tables (property, child table, child columns) match the reflection map of a plain twin graph. The AOT smoke now includes a nested [BulkCopyable] child, so the native run proves the nested descriptor path is IL2070-clean too. 275 unit tests green; all 5 TFMs build clean. Refs #95. Docs: ADR 0006. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tests/Wolfgang.Etl.SqlBulkCopy.Tests.Fuzz (net10.0, CsCheck 4.0.0) with two mapping invariants asserted over large numbers of generated inputs: - QualifiedTableName bracket-quoting round-trips for arbitrary schema/table identifiers (un-quoting recovers the original — catches any escaping bug that lets an identifier break out of its brackets). - Every mapped column's getter returns exactly what reflection reads from the same property, across random record values (exercises the compiled / source-generated getter path). Plus .github/workflows/fuzz.yaml (weekly cron + workflow_dispatch, CsCheck case count from CsCheck_Iterations, reports uploaded as an artifact), mirroring the ETL-DbClient fuzz pattern. Both properties hold at 20k local iterations. Refs #81. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds pr-benchmarks.yaml (canonical fleet workflow, adapted): runs the DB-free micro-benchmarks (PropertyGetter, SliceList) on the PR HEAD and on the merge-base with the target branch, computes per-benchmark time+allocation deltas, and posts a single PR comment that's replaced (not appended) on each push. Informational gate for now; threshold-based failure is a follow-up once runs have sized the normal noise. Uses the repo's current action pins. Refs #106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an attest-build-provenance job to release.yaml (canonical fleet pattern): after publish, it downloads the packed nuget-packages artifact and attests the .nupkg/.snupkg provenance via GitHub OIDC + Sigstore keyless signing — proving they were built by this workflow at this commit. Requires NO code-signing cert. SECURITY.md gains a consumer-side verification section (gh attestation verify), and explicitly documents that NuGet author-signing (which needs a cert) is out of scope — the SLSA attestation provides the equivalent build-integrity guarantee. SBOM (CycloneDX) was already emitted by release.yaml. Refs #90 (signing sub-task intentionally deferred; needs a cert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tests/Wolfgang.Etl.SqlBulkCopy.Tests.Concurrency (net10.0, Microsoft.Coyote 1.7.11) driving TypeMap's ConcurrentDictionary cache through Coyote's TestingEngine: under any interleaving of N concurrent TypeMap.Create callers for the same type, every caller must receive the SAME cached instance (GetOrAdd race-freedom). Runs at 100 iterations per-PR (Category=Concurrency) and 10,000 on the weekly concurrency.yaml + workflow_dispatch (COYOTE_ITERATIONS). Coyote explores schedules in-process, so no SQL/Docker is needed for the cache races; the realistic sustained-load angle is covered by gc-profile.yaml (#94). Passes locally at 3,000 iterations (0 bugs). Refs #89 (24h self-hosted soak intentionally dropped, matching the fleet's canonical Coyote-only approach). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…log line - Gate the aot-consumer job on needs.pack-and-validate.outputs.has-packages so it is skipped (not failed) in repos with no packages, matching publish-nuget. - Replace the always-zero "exited $?" line (set -e aborts on failure) with a static success message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Chris-Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com>
Address review feedback on BulkCopyAccessorGenerator.cs: the repeated IsStatic/IsIndexer/GetMethod-null(/[NotMapped]) or-chains are factored into intention-revealing predicates, mirroring TypeMap.IsReadableInstanceProperty on the reflection path so both providers filter identically: - IsReadableInstanceProperty — instance property with a getter - IsMappableProperty — readable + not [NotMapped] (was duplicated verbatim) - IsAccessorEmittableProperty — readable + non-ref-like + reachable getter Each of the four call sites collapses to one named line. Behaviour is unchanged (DescriptorConformanceTests: generated map == reflection map, 276 tests green). Also reflow the two .Add(string.Join(...)) blocks to the Allman multi-line argument style (opening paren on its own line) per review feedback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Raise the four Option-B descriptor types to 100% line coverage so the per-module 90% gate passes at the vNext->main release: - ColumnMap: descriptor ctor throws when no generated getter is registered, and when an enum column type has no registered converter. - NestedTableMap: descriptor ctor throws when no generated getter is registered. - GeneratedAccessorRegistry.RegisterEnumConverter / GeneratedTypeMapRegistry.Register: ArgumentNullException guards. Adds TestModels/UnregisteredProbeEnum (never [BulkCopyable], never registered) to drive the enum-converter defensive branch. +7 tests (283 total, all green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Version 0.4.0 -> 0.5.0 (Version / AssemblyVersion / FileVersion; AssemblyVersion tracks the minor in 0.x per the netfx binding policy). - Move the source-gen AOT surface from PublicAPI.Unshipped -> Shipped (BulkCopyableAttribute, GeneratedAccessorRegistry, and the descriptor types). - Finalize CHANGELOG [0.5.0]: AOT source-generated accessors + reflection-free descriptors (Added); SLSA build-provenance attestation on release (Security). - ADR 0006 Proposed -> Accepted (0.5.0); ADR 0004 -> Superseded by 0006. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: source-generate type descriptors; quarantine reflection for AOT (ADR 0006, #95)
release: prepare v0.5.0
test: continuous property-based fuzz via CsCheck (#81)
ci: per-PR benchmark delta vs merge-base (#106)
…ifferential ci: cross-platform / multi-arch differential (#91)
ci: sustained-load GC / allocation profiling via Docker SQL (#94)
test: Coyote systematic-concurrency suite (#89)
ci(release): gate NuGet publish on the Native AOT smoke
ci: SLSA build-provenance attestation on release (#90, minus signing)
- pr-benchmarks.yaml: match BenchmarkDotNet's *-report-full-compressed.json output (was *-report.json → empty delta); full base-branch fetch instead of --depth=100 (could miss merge-base on long PRs); --paginate the marker-comment lookup so it updates instead of duplicating past page 1. - gc-profile.yaml: fail the job on a workload crash (after stopping counters) instead of swallowing the exit code, so the scheduled signal is reliable. - fuzz.yaml, aot-smoke.yaml: pin actions/checkout to the repo-standard SHA (3d3c42e) for supply-chain consistency. Workflow-only; no library/test/package change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`dotnet test ... | tee reports/*.log` reported tee's zero exit, masking a failing fuzz/Coyote run and leaving the scheduled workflow green. Add `set -eo pipefail` so a test failure propagates through the pipe and fails the step. (aot-smoke.yaml and release.yaml already set pipefail on their tee pipes; no other `| tee` needs it.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR benchmark deltaNo benchmarks ran on either side. |
There was a problem hiding this comment.
Pull request overview
This PR prepares the v0.5.0 release by adding Native AOT / trimming support to the type-mapping hot path via bundled source generation, and by hardening the repo’s quality/CI posture with new test suites (AOT smoke, doc-example compilation, fuzz, and concurrency) plus updated security and release documentation.
Changes:
- Introduces
[BulkCopyable]plus a bundled Roslyn incremental source generator that registers property getters, enum converters, and (when eligible) full type descriptors to avoid runtime IL emission/reflection onnet5.0+. - Updates runtime mapping to prefer generated descriptors/accessors, and adds conformance + end-to-end tests (descriptor parity, generator registration, fuzz, Coyote concurrency, Native AOT smoke, XML-doc snippet compilation).
- Bumps package version to 0.5.0, updates PublicAPI tracking + ADRs + README/SECURITY/CHANGELOG.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/GcProfileWorkload/Widget.cs | Adds workload entity type used by GC profiling harness. |
| tools/GcProfileWorkload/Program.cs | Adds sustained-load SQL Server/Testcontainers workload driver for GC profiling. |
| tools/GcProfileWorkload/GcProfileWorkload.csproj | Adds standalone workload project referencing runtime + Testcontainers/SqlClient. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/Wolfgang.Etl.SqlBulkCopy.Tests.Unit.csproj | References source generator as analyzer for end-to-end generator testing. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/TestModels/UnregisteredProbeEnum.cs | Adds enum fixture to exercise defensive “no generated converter” branch. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/TestModels/GeneratedAccessorProbeRecord.cs | Adds isolated model for registry tests to avoid cross-test leakage. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/TestModels/DescriptorConformanceFixtures.cs | Adds paired BulkCopyable/plain fixtures for descriptor-vs-reflection parity tests. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/TestModels/BulkCopyableFixture.cs | Adds simple [BulkCopyable] fixture for generator registration tests. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/TestModels/BulkCopyableEnumFixture.cs | Adds enum-bearing [BulkCopyable] fixture for enum converter coverage. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/NestedTableMapTests.cs | Adds negative test for descriptor ctor when getter is missing. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/GeneratedTypeMapRegistryTests.cs | Adds argument-null tests for generated type map registry. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/GeneratedAccessorRegistryTests.cs | Adds tests verifying generated accessor preference + null-guard behavior. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/DescriptorConformanceTests.cs | Adds tests asserting generated descriptors match reflection-built maps. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/ColumnMapTests.cs | Adds negative tests for missing generated getter/enum converter in descriptor ctor. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/BulkCopyAccessorGeneratorTests.cs | Adds end-to-end generator registration assertions for [BulkCopyable] fixtures. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/BulkCopyAccessorConformanceTests.cs | Adds conformance checks: generated getters/converters match reflection. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Fuzz/Wolfgang.Etl.SqlBulkCopy.Tests.Fuzz.csproj | Adds CsCheck-driven fuzz test project (net10.0). |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Fuzz/MappingFuzz.cs | Adds property-based fuzz over qualified table quoting + getter correctness. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Fuzz/FuzzRecord.cs | Adds fixed record shape used by fuzz properties. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.DocExamples/Wolfgang.Etl.SqlBulkCopy.Tests.DocExamples.csproj | Adds Roslyn-based doc-example compilation test project (net8.0). |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.DocExamples/DocExampleTests.cs | Extracts and compiles <example><code> snippets to prevent doc rot. |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Concurrency/Wolfgang.Etl.SqlBulkCopy.Tests.Concurrency.csproj | Adds Coyote systematic concurrency test project (net10.0). |
| tests/Wolfgang.Etl.SqlBulkCopy.Tests.Concurrency/TypeMapCacheConcurrencyTests.cs | Adds Coyote-driven cache race test for TypeMap.Create caching invariants. |
| tests/Wolfgang.Etl.SqlBulkCopy.AotSmoke/Wolfgang.Etl.SqlBulkCopy.AotSmoke.csproj | Adds Native AOT publishable smoke consumer project (net10.0). |
| tests/Wolfgang.Etl.SqlBulkCopy.AotSmoke/Program.cs | Implements AOT smoke program exercising generated getters, enum converter, nested descriptor. |
| src/Wolfgang.Etl.SqlBulkCopy/Wolfgang.Etl.SqlBulkCopy.csproj | Bumps version to 0.5.0; enables package validation; embeds generator into package. |
| src/Wolfgang.Etl.SqlBulkCopy/TypeMap.cs | Prefers generated type descriptors; annotates reflection path for trimming/AOT guidance. |
| src/Wolfgang.Etl.SqlBulkCopy/PublicAPI.Shipped.txt | Ships new public surface for generator/descriptor infrastructure + attribute. |
| src/Wolfgang.Etl.SqlBulkCopy/Properties/AssemblyInfo.cs | Extends InternalsVisibleTo for new test/tool assemblies. |
| src/Wolfgang.Etl.SqlBulkCopy/Polyfills/UnconditionalSuppressMessageAttribute.cs | Adds polyfill for trim-warning suppression on pre-net5 targets. |
| src/Wolfgang.Etl.SqlBulkCopy/Polyfills/RequiresUnreferencedCodeAttribute.cs | Adds polyfill for trimming annotation on pre-net5 targets. |
| src/Wolfgang.Etl.SqlBulkCopy/NestedTableMap.cs | Adds descriptor-based ctor using generated getters; refactors enumeration logic. |
| src/Wolfgang.Etl.SqlBulkCopy/GeneratedTypeMapRegistry.cs | Adds registry for generated type descriptors (public register, internal query). |
| src/Wolfgang.Etl.SqlBulkCopy/GeneratedTypeDescriptor.cs | Adds descriptor model for schema/table/columns/nested tables. |
| src/Wolfgang.Etl.SqlBulkCopy/GeneratedNestedTableDescriptor.cs | Adds nested-table descriptor model. |
| src/Wolfgang.Etl.SqlBulkCopy/GeneratedColumnDescriptor.cs | Adds column descriptor model. |
| src/Wolfgang.Etl.SqlBulkCopy/GeneratedAccessorRegistry.cs | Adds registry for generated getters + enum converters (public register, internal query). |
| src/Wolfgang.Etl.SqlBulkCopy/ColumnMap.cs | Prefers generated getters/converters; adds descriptor-based ctor. |
| src/Wolfgang.Etl.SqlBulkCopy/BulkCopyableAttribute.cs | Adds opt-in attribute driving generation. |
| src/Wolfgang.Etl.SqlBulkCopy.SourceGenerator/Wolfgang.Etl.SqlBulkCopy.SourceGenerator.csproj | Adds netstandard2.0 incremental source generator project. |
| src/Wolfgang.Etl.SqlBulkCopy.SourceGenerator/Polyfills/IsExternalInit.cs | Adds IsExternalInit polyfill for records/init in netstandard2.0 generator. |
| src/Wolfgang.Etl.SqlBulkCopy.SourceGenerator/BulkCopyAccessorGenerator.cs | Implements generator: emits getters, enum converters, optional descriptors + module initializer. |
| SECURITY.md | Documents supply-chain verification (SBOM + SLSA provenance verification steps). |
| README.md | Documents Native AOT/trimming guidance and [BulkCopyable] usage. |
| ETL-SqlBulkCopy.slnx | Adds new generator + test projects to solution. |
| docs/adr/index.md | Adds ADR 0006 entry and marks 0004 as superseded. |
| docs/adr/0006-source-generated-property-accessors.md | Adds ADR describing the new source-generated AOT approach. |
| docs/adr/0004-compiled-property-getters-and-native-aot.md | Marks ADR 0004 as superseded by ADR 0006. |
| CHANGELOG.md | Adds v0.5.0 release notes documenting generator/AOT and SLSA additions. |
Suppressed comments (1)
src/Wolfgang.Etl.SqlBulkCopy.SourceGenerator/BulkCopyAccessorGenerator.cs:152
- EncodeDescriptor's XML doc lists "nested-table properties" as making a type ineligible for descriptor generation, but the implementation supports nested-table descriptors (and only rejects nested graphs when they are not fully generatable). This mismatch makes it harder to understand when a descriptor will be emitted.
/// Encodes the type's generated descriptor, or returns an empty string when
/// the type is not eligible for descriptor generation in this pass (has
/// nested-table properties, inherits mapped properties, has no mappable
/// columns, has duplicate column names, or is <c>[NotMapped]</c>). Those
/// types fall back to the reflection path, which produces the identical map.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
All four review findings are fixed as a stacked series on vNext (merge these before this release PR):
Resolving these threads; the fixes land in this PR once #222–#225 merge into vNext. |
v0.5.0
Headline — Native AOT support via source-generated accessors (ADR 0006, #95)
Mark a record
[BulkCopyable]and the bundled source generator emits its property getters, enum→underlying converters, and a full type descriptor at compile time; the loader prefers these over the runtimeExpression.Compilegetter, keeping the marked type's hot path free of runtime IL emission (net5.0+). Opt-in and additive — unmarked types are unchanged, and the generator ships inside the existing package (no second NuGet). Reflection remains the fallback (quarantined behind[RequiresUnreferencedCode]), and aDescriptorConformanceTestssuite asserts the generated map == the reflection map.CI / quality hardening (thorough-review tier)
release.yamlpublish ([Maintenance] testing: Trim / AOT compatibility verification (PublishAot smoke) #95/[Maintenance] security: Supply chain hardening — SBOM + SLSA attestation #90).Release metadata
Notes
Closes #220