diff --git a/.github/workflows/on-push-do-ci-build-pg15-jsonnet-eventsourcing-closed-shape.yml b/.github/workflows/on-push-do-ci-build-pg15-jsonnet-eventsourcing-closed-shape.yml deleted file mode 100644 index 10d04bdd94..0000000000 --- a/.github/workflows/on-push-do-ci-build-pg15-jsonnet-eventsourcing-closed-shape.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Build & Test - NET9 - PG15 - Newtonsoft - EventSourcing (Closed-Shape) - -# #4430: parallel CI lane that runs the event-sourcing suite with the -# closed-shape event-storage flag on (StoreOptions.Events.UseClosedShapeStorage = true). -# Guards against drift between codegen + closed-shape coverage so the v10 default -# flip lands clean. Same lane on the PG15 + Newtonsoft matrix entry so -# partition-related regressions (which depend on the older PG line as well as -# the serializer) get caught. - -on: - push: - branches: - - master - - "7.0" - paths-ignore: - - 'documentation/**' - - 'docs/**' - - 'azure-pipelines.yml' - pull_request: - branches: - - master - - "7.0" - paths-ignore: - - 'documentation/**' - - 'docs/**' - - 'azure-pipelines.yml' - -env: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - pg_db: marten_testing - pg_user: postgres - CONFIGURATION: Release - FRAMEWORK: net9.0 - DISABLE_TEST_PARALLELIZATION: true - DEFAULT_SERIALIZER: "Newtonsoft" - NUKE_TELEMETRY_OPTOUT: true - MARTEN_USE_CLOSED_SHAPE_STORAGE: true - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 - - services: - postgres: - image: postgres:15-alpine - ports: - - 5432:5432 - env: - POSTGRES_HOST_AUTH_METHOD: trust - POSTGRES_DB: ${{ env.pg_db }} - NAMEDATALEN: 150 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - --user postgres - - steps: - - uses: actions/checkout@v6 - - - name: Install .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 9.0.x - 10.0.x - - - name: Install .NET Aspire workload - run: | - dotnet workload update - dotnet workload install aspire - - - name: Optimize database for running tests faster - run: | - PG_CONTAINER_NAME=$(docker ps --filter expose=5432/tcp --format {{.Names}}) - docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nfsync = off' >> /var/lib/postgresql/data/postgresql.conf" - docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nfull_page_writes = off' >> /var/lib/postgresql/data/postgresql.conf" - docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nsynchronous_commit = off' >> /var/lib/postgresql/data/postgresql.conf" - docker container restart $PG_CONTAINER_NAME - shell: bash - - - name: compile - run: ./build.sh compile-project --project src/EventSourcingTests/EventSourcingTests.csproj - shell: bash - - - name: test-event-sourcing - if: ${{ success() || failure() }} - run: ./build.sh test-event-sourcing - shell: bash diff --git a/.github/workflows/on-push-do-ci-build-pgLatest-systemtextjson-eventsourcing-closed-shape.yml b/.github/workflows/on-push-do-ci-build-pgLatest-systemtextjson-eventsourcing-closed-shape.yml deleted file mode 100644 index 0cdefc33b1..0000000000 --- a/.github/workflows/on-push-do-ci-build-pgLatest-systemtextjson-eventsourcing-closed-shape.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Build & Test - NET10 - PGLatest - System.Text.Json - Event Sourcing (Closed-Shape) - -# #4430: parallel CI lane that runs the event-sourcing suite with the -# closed-shape event-storage flag on (StoreOptions.Events.UseClosedShapeStorage = true). -# Guards against drift between codegen + closed-shape coverage so the v10 default -# flip lands clean. - -on: - push: - branches: - - master - - "7.0" - paths-ignore: - - 'documentation/**' - - 'docs/**' - - 'azure-pipelines.yml' - pull_request: - branches: - - master - - "7.0" - paths-ignore: - - 'documentation/**' - - 'docs/**' - - 'azure-pipelines.yml' - -env: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - pg_db: marten_testing - pg_user: postgres - CONFIGURATION: Release - FRAMEWORK: net10.0 - DISABLE_TEST_PARALLELIZATION: true - DEFAULT_SERIALIZER: "SystemTextJson" - NUKE_TELEMETRY_OPTOUT: true - MARTEN_USE_CLOSED_SHAPE_STORAGE: true - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 - - services: - postgres: - image: postgres:latest - ports: - - 5432:5432 - env: - POSTGRES_HOST_AUTH_METHOD: trust - POSTGRES_DB: ${{ env.pg_db }} - NAMEDATALEN: 150 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - --user postgres - - steps: - - uses: actions/checkout@v6 - - - name: Install .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 9.0.x - 10.0.x - - - name: Install .NET Aspire workload - run: | - dotnet workload update - dotnet workload install aspire - - # - name: Optimize database for running tests faster - # run: | - # PG_CONTAINER_NAME=$(docker ps --filter expose=5432/tcp --format {{.Names}}) - # docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nfsync = off' >> /var/lib/postgresql/data/postgresql.conf" - # docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nfull_page_writes = off' >> /var/lib/postgresql/data/postgresql.conf" - # docker exec $PG_CONTAINER_NAME bash -c "echo -e '\nsynchronous_commit = off' >> /var/lib/postgresql/data/postgresql.conf" - # docker container restart $PG_CONTAINER_NAME - # shell: bash - - - name: compile - run: ./build.sh compile-project --project src/EventSourcingTests/EventSourcingTests.csproj - shell: bash - - - name: test-event-sourcing - if: ${{ success() || failure() }} - run: ./build.sh test-event-sourcing - shell: bash diff --git a/src/DaemonTests/Bugs/Bug_4428_rich_storage_side_effect_events.cs b/src/DaemonTests/Bugs/Bug_4428_rich_storage_side_effect_events.cs index 277238292f..50b86ae551 100644 --- a/src/DaemonTests/Bugs/Bug_4428_rich_storage_side_effect_events.cs +++ b/src/DaemonTests/Bugs/Bug_4428_rich_storage_side_effect_events.cs @@ -17,8 +17,7 @@ namespace DaemonTests.Bugs; /// /// Regression for jasperfx/marten#4428. /// -/// When StoreOptions.Events.UseClosedShapeStorage = true and -/// AppendMode = EventAppendMode.Rich, the async-projection +/// Under AppendMode = EventAppendMode.Rich, the async-projection /// side-effect replay path (JasperFx.Events EventSlice.BuildOperations /// → IProjectionBatch.QuickAppendEventWithVersion → /// Marten ProjectionBatch.QuickAppendEventWithVersion → @@ -27,8 +26,8 @@ namespace DaemonTests.Bugs; /// NotImplementedException. The closed-shape Rich implementation was /// stubbed out and the assumption "the Rich appender only calls AppendEvent" /// missed the daemon's side-effect replay path. This test asserts the -/// raised side-effect event lands in mt_events when running the rebuild -/// under UseClosedShapeStorage = true + Rich. +/// raised side-effect event lands in mt_events when running a Rich-mode +/// rebuild. /// public class Bug_4428_rich_storage_side_effect_events: OneOffConfigurationsContext { @@ -37,10 +36,6 @@ public async Task raised_side_effect_event_persists_under_closed_shape_rich() { StoreOptions(opts => { - // Force the closed-shape Rich path explicitly so the test reproduces - // #4428 regardless of the suite-wide env flag (which only affects - // UseClosedShapeStorage, not AppendMode). - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Projections.Add(ProjectionLifecycle.Async); }); diff --git a/src/EventSourcingTests/Bugs/Bug_4411_closed_shape_read_side.cs b/src/EventSourcingTests/Bugs/Bug_4411_closed_shape_read_side.cs index f90676ce31..0cf97b2927 100644 --- a/src/EventSourcingTests/Bugs/Bug_4411_closed_shape_read_side.cs +++ b/src/EventSourcingTests/Bugs/Bug_4411_closed_shape_read_side.cs @@ -24,11 +24,12 @@ namespace EventSourcingTests.Bugs; /// /// /// -/// We can't yet flip UseClosedShapeStorage on end-to-end because -/// the closed-shape write path is still stubbed (#4413 / #4414). So the -/// test uses the codegen path to append events, then constructs a -/// directly and runs the same -/// select-from-mt_events query against it, comparing the resolved +/// Originally written before the closed-shape write path was wired +/// (when only the read side could be exercised), the test still pins +/// the read-side adapter behavior: it uses the standard append path to +/// insert events, then constructs a +/// directly and runs the +/// same select-from-mt_events query against it, comparing the resolved /// IEvents field-by-field. /// /// diff --git a/src/EventSourcingTests/Bugs/Bug_4412_closed_shape_rich_write_path.cs b/src/EventSourcingTests/Bugs/Bug_4412_closed_shape_rich_write_path.cs index c77de319d5..724ea561aa 100644 --- a/src/EventSourcingTests/Bugs/Bug_4412_closed_shape_rich_write_path.cs +++ b/src/EventSourcingTests/Bugs/Bug_4412_closed_shape_rich_write_path.cs @@ -12,10 +12,7 @@ namespace EventSourcingTests.Bugs; /// /// End-to-end smoke test for the closed-shape Rich-mode write path -/// (#4412 + #4413): flips UseClosedShapeStorage = true, sets -/// AppendMode = Rich (the closed-shape adapter currently only wires -/// the Rich path's write operations — Quick / QuickWithServerTimestamps -/// land in #4414 / #4415), and exercises: +/// (#4412 + #4413): sets AppendMode = Rich and exercises: /// /// /// via @@ -35,7 +32,6 @@ public async Task start_stream_then_append_then_read_back_round_trip_guid_identi { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; }); @@ -101,7 +97,6 @@ public async Task start_stream_then_append_then_read_back_round_trip_string_iden { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Events.StreamIdentity = StreamIdentity.AsString; }); diff --git a/src/EventSourcingTests/Bugs/Bug_4414_closed_shape_quick_write_path.cs b/src/EventSourcingTests/Bugs/Bug_4414_closed_shape_quick_write_path.cs index 008cb007f4..2449705ffc 100644 --- a/src/EventSourcingTests/Bugs/Bug_4414_closed_shape_quick_write_path.cs +++ b/src/EventSourcingTests/Bugs/Bug_4414_closed_shape_quick_write_path.cs @@ -25,7 +25,6 @@ public async Task quick_round_trip_guid_identity() { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Quick; }); @@ -72,7 +71,6 @@ public async Task quick_round_trip_string_identity() { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Quick; opts.Events.StreamIdentity = StreamIdentity.AsString; }); @@ -113,7 +111,6 @@ public async Task quick_round_trip_with_scalar_metadata() { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Quick; opts.Events.MetadataConfig.CausationIdEnabled = true; opts.Events.MetadataConfig.CorrelationIdEnabled = true; diff --git a/src/EventSourcingTests/Bugs/Bug_4415_closed_shape_quick_with_server_timestamps.cs b/src/EventSourcingTests/Bugs/Bug_4415_closed_shape_quick_with_server_timestamps.cs index 88cc0b04d9..38cbb05d06 100644 --- a/src/EventSourcingTests/Bugs/Bug_4415_closed_shape_quick_with_server_timestamps.cs +++ b/src/EventSourcingTests/Bugs/Bug_4415_closed_shape_quick_with_server_timestamps.cs @@ -30,7 +30,6 @@ public async Task quick_with_server_timestamps_round_trip_guid_identity() { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; // AppendMode defaults to QuickWithServerTimestamps in v9, but // be explicit here to pin the test against the right path. opts.Events.AppendMode = EventAppendMode.QuickWithServerTimestamps; @@ -80,7 +79,6 @@ public async Task quick_with_server_timestamps_default_v9_config_works() // make no other config tweaks. This is the headline "drop-in" path. StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; }); var streamId = Guid.NewGuid(); diff --git a/src/EventSourcingTests/Bugs/Bug_4416_closed_shape_metadata_binders.cs b/src/EventSourcingTests/Bugs/Bug_4416_closed_shape_metadata_binders.cs index 4effe1c6e1..30765446f5 100644 --- a/src/EventSourcingTests/Bugs/Bug_4416_closed_shape_metadata_binders.cs +++ b/src/EventSourcingTests/Bugs/Bug_4416_closed_shape_metadata_binders.cs @@ -30,7 +30,6 @@ public async Task scalar_metadata_binders_round_trip_under_closed_shape_storage( { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Events.MetadataConfig.CausationIdEnabled = true; opts.Events.MetadataConfig.CorrelationIdEnabled = true; @@ -76,7 +75,6 @@ public async Task headers_round_trip_under_closed_shape_storage() // #4416 part 2). StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Events.MetadataConfig.HeadersEnabled = true; }); @@ -113,7 +111,6 @@ public async Task event_skipping_flag_does_not_break_rich_closed_shape_path() // adapter must NOT fail the descriptor build. StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Events.EnableEventSkippingInProjectionsOrSubscriptions = true; }); @@ -139,7 +136,6 @@ public async Task all_metadata_binders_together_round_trip() // binder wired in #4416 part 1 + part 2. StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Rich; opts.Events.MetadataConfig.CausationIdEnabled = true; opts.Events.MetadataConfig.CorrelationIdEnabled = true; diff --git a/src/EventSourcingTests/Bugs/Bug_4417_closed_shape_dcb_hstore.cs b/src/EventSourcingTests/Bugs/Bug_4417_closed_shape_dcb_hstore.cs index 0d989cede2..df174fc9c2 100644 --- a/src/EventSourcingTests/Bugs/Bug_4417_closed_shape_dcb_hstore.cs +++ b/src/EventSourcingTests/Bugs/Bug_4417_closed_shape_dcb_hstore.cs @@ -42,7 +42,6 @@ public async Task hstore_tags_round_trip_under_closed_shape_flag() { StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AddEventType(); opts.Events.AddEventType(); @@ -101,7 +100,6 @@ public async Task hstore_tags_round_trip_with_quick_append_mode() // code paths.) StoreOptions(opts => { - opts.Events.UseClosedShapeStorage = true; opts.Events.AppendMode = EventAppendMode.Quick; opts.Events.AddEventType(); diff --git a/src/EventSourcingTests/QuickAppend/quick_appending_events_workflow_specs.cs b/src/EventSourcingTests/QuickAppend/quick_appending_events_workflow_specs.cs index 1cc84c2654..6ce39d1ebf 100644 --- a/src/EventSourcingTests/QuickAppend/quick_appending_events_workflow_specs.cs +++ b/src/EventSourcingTests/QuickAppend/quick_appending_events_workflow_specs.cs @@ -10,7 +10,7 @@ using JasperFx.Events; using Marten; using Marten.Events; -using Marten.Events.CodeGeneration; +using Marten.EventStorage; using Marten.Events.Operations; using Marten.Exceptions; using Marten.Internal; @@ -57,7 +57,7 @@ public async Task can_fetch_stream_async(TestCase @case) await @case.StartNewStream(); await using var query = @case.Store.QuerySession(); - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var handler = builder.QueryForStream(@case.ToEventStream()); var state = await query.As().ExecuteHandlerAsync(handler, CancellationToken.None); @@ -73,7 +73,7 @@ public async Task can_insert_a_new_stream(TestCase @case) @case.StartNewStream(); var stream = @case.CreateNewStream(); - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var op = builder.InsertStream(stream); await using var session = @case.Store.LightweightSession(); @@ -92,7 +92,7 @@ public async Task can_update_the_version_of_an_existing_stream_sad_path(TestCase stream.ExpectedVersionOnServer = 3; // it's actually 4, so this should fail stream.Version = 10; - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var op = builder.UpdateStreamVersion(stream); await using var session = @case.Store.LightweightSession(); diff --git a/src/EventSourcingTests/appending_events_workflow_specs.cs b/src/EventSourcingTests/appending_events_workflow_specs.cs index a3de14043a..551a9e9fda 100644 --- a/src/EventSourcingTests/appending_events_workflow_specs.cs +++ b/src/EventSourcingTests/appending_events_workflow_specs.cs @@ -12,7 +12,7 @@ using JasperFx.Events; using Marten; using Marten.Events; -using Marten.Events.CodeGeneration; +using Marten.EventStorage; using Marten.Events.Operations; using Marten.Exceptions; using Marten.Internal; @@ -60,7 +60,7 @@ public async Task can_fetch_stream_async(TestCase @case) await @case.StartNewStream(); await using var query = @case.Store.QuerySession(); - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var handler = builder.QueryForStream(@case.ToEventStream()); var state = await query.As().ExecuteHandlerAsync(handler, CancellationToken.None); @@ -76,7 +76,7 @@ public async Task can_insert_a_new_stream(TestCase @case) await @case.StartNewStream(); var stream = @case.CreateNewStream(); - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var op = builder.InsertStream(stream); await using var session = @case.Store.LightweightSession(); @@ -95,7 +95,7 @@ public async Task can_update_the_version_of_an_existing_stream_happy_path(TestCa stream.ExpectedVersionOnServer = 4; stream.Version = 10; - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var op = builder.UpdateStreamVersion(stream); await using var session = @case.Store.LightweightSession(); @@ -119,7 +119,7 @@ public async Task can_update_the_version_of_an_existing_stream_sad_path(TestCase stream.ExpectedVersionOnServer = 3; // it's actually 4, so this should fail stream.Version = 10; - var builder = EventDocumentStorageGenerator.GenerateStorage(@case.Store.Options); + var builder = new ClosedShapeEventDocumentStorage(@case.Store.Options); var op = builder.UpdateStreamVersion(stream); await using var session = @case.Store.LightweightSession(); diff --git a/src/Marten.Testing/Harness/DefaultStoreFixture.cs b/src/Marten.Testing/Harness/DefaultStoreFixture.cs index 4394c17201..a9001d1e23 100644 --- a/src/Marten.Testing/Harness/DefaultStoreFixture.cs +++ b/src/Marten.Testing/Harness/DefaultStoreFixture.cs @@ -21,9 +21,6 @@ public class DefaultStoreFixture: IAsyncLifetime opts.AutoCreateSchemaObjects = AutoCreate.All; opts.DatabaseSchemaName = "string_events"; opts.Events.StreamIdentity = StreamIdentity.AsString; - - if (TestsSettings.UseClosedShapeStorage) - opts.Events.UseClosedShapeStorage = true; }); using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); @@ -42,9 +39,6 @@ public Task InitializeAsync() opts.GeneratedCodeMode = TypeLoadMode.Auto; opts.ApplicationAssembly = GetType().Assembly; - - if (TestsSettings.UseClosedShapeStorage) - opts.Events.UseClosedShapeStorage = true; }); // Do this exactly once and no more. diff --git a/src/Marten.Testing/Harness/IntegrationContext.cs b/src/Marten.Testing/Harness/IntegrationContext.cs index ba4dd68140..cfaff2362f 100644 --- a/src/Marten.Testing/Harness/IntegrationContext.cs +++ b/src/Marten.Testing/Harness/IntegrationContext.cs @@ -73,9 +73,6 @@ protected IDocumentStore SeparateStore() opts.GeneratedCodeMode = TypeLoadMode.Auto; opts.ApplicationAssembly = GetType().Assembly; - - if (TestsSettings.UseClosedShapeStorage) - opts.Events.UseClosedShapeStorage = true; }); } @@ -128,14 +125,6 @@ protected string StoreOptions(Action configure) configure(options); - // #4417 / #4418: opt-in suite sweep under the closed-shape adapter. - // Applied AFTER the test's configure so an individual test that - // explicitly turns it off wins. - if (TestsSettings.UseClosedShapeStorage && !options.Events.UseClosedShapeStorage) - { - options.Events.UseClosedShapeStorage = true; - } - _store = new DocumentStore(options); Disposables.Add(_store); diff --git a/src/Marten.Testing/Harness/OneOffConfigurationsContext.cs b/src/Marten.Testing/Harness/OneOffConfigurationsContext.cs index 7502986a4e..62f8688520 100644 --- a/src/Marten.Testing/Harness/OneOffConfigurationsContext.cs +++ b/src/Marten.Testing/Harness/OneOffConfigurationsContext.cs @@ -49,11 +49,6 @@ protected DocumentStore SeparateStore(Action configure = null) configure?.Invoke(options); - if (TestsSettings.UseClosedShapeStorage && !options.Events.UseClosedShapeStorage) - { - options.Events.UseClosedShapeStorage = true; - } - var store = new DocumentStore(options); _disposables.Add(store); @@ -89,16 +84,6 @@ private DocumentStore storeOptions(Action configure, bool cleanAll configure(options); - // #4417 / #4418: run the whole event-sourcing suite under the - // closed-shape adapter when MARTEN_USE_CLOSED_SHAPE_STORAGE=true. - // Applied AFTER the test's configure callback so individual tests - // that explicitly disable the flag (or set conflicting options) - // win. - if (TestsSettings.UseClosedShapeStorage && !options.Events.UseClosedShapeStorage) - { - options.Events.UseClosedShapeStorage = true; - } - if (cleanAll) { using var conn = new NpgsqlConnection(ConnectionSource.ConnectionString); diff --git a/src/Marten.Testing/Harness/StoreFixture.cs b/src/Marten.Testing/Harness/StoreFixture.cs index e11c41b44d..446e793cba 100644 --- a/src/Marten.Testing/Harness/StoreFixture.cs +++ b/src/Marten.Testing/Harness/StoreFixture.cs @@ -31,11 +31,6 @@ public DocumentStore Store { if (_store == null) { - if (TestsSettings.UseClosedShapeStorage && !Options.Events.UseClosedShapeStorage) - { - Options.Events.UseClosedShapeStorage = true; - } - _store = new DocumentStore(Options); } diff --git a/src/Marten.Testing/Harness/TestsSettings.cs b/src/Marten.Testing/Harness/TestsSettings.cs index 53a001a990..c4b738f2c3 100644 --- a/src/Marten.Testing/Harness/TestsSettings.cs +++ b/src/Marten.Testing/Harness/TestsSettings.cs @@ -36,27 +36,5 @@ public static SerializerType SerializerType } } - private static bool? useClosedShapeStorage; - - /// - /// Reads MARTEN_USE_CLOSED_SHAPE_STORAGE. When true, the - /// harness contexts flip StoreOptions.Events.UseClosedShapeStorage - /// on after the test's own configure callback runs — so we can - /// run the whole event-sourcing suite under the closed-shape adapter - /// (#4417 / #4418) without touching the individual tests. - /// - public static bool UseClosedShapeStorage - { - get - { - if (useClosedShapeStorage.HasValue) - return useClosedShapeStorage.Value; - - var env = Environment.GetEnvironmentVariable("MARTEN_USE_CLOSED_SHAPE_STORAGE"); - useClosedShapeStorage = string.Equals(env, "true", StringComparison.OrdinalIgnoreCase); - return useClosedShapeStorage.Value; - } - } - } } diff --git a/src/Marten/EventStorage/ClosedShapeEventDocumentStorage.cs b/src/Marten/EventStorage/ClosedShapeEventDocumentStorage.cs index 74a992130c..43c1cb2b6d 100644 --- a/src/Marten/EventStorage/ClosedShapeEventDocumentStorage.cs +++ b/src/Marten/EventStorage/ClosedShapeEventDocumentStorage.cs @@ -18,8 +18,8 @@ namespace Marten.EventStorage; /// /// Non-codegen subclass that delegates /// the write-path methods to a closed-shape -/// instance. Constructed by when -/// StoreOptions.Events.UseClosedShapeStorage is on. +/// instance. Constructed by as the sole event +/// storage adapter in v9. /// /// /// @@ -36,14 +36,12 @@ namespace Marten.EventStorage; /// chosen via . /// /// -/// Read-side note: and -/// are still codegen-emitted -/// in v9 even with the closed-shape flag on. The closed-shape work in -/// W4 covers the write path; the read-side equivalent lives with W5 -/// (Marten.SourceGenerator) — the read-side method body needs per-event -/// type info that source-gen will provide. Until then, the codegen -/// path is still required for the read selector — see open question (3) -/// on #4410. +/// Read-side / +/// walk +/// / +/// over a column list +/// derived from ; no codegen is +/// involved on either the read or the write path. /// /// internal sealed class ClosedShapeEventDocumentStorage: EventDocumentStorage diff --git a/src/Marten/EventStorage/Dialects/PostgresEventStoreDialect.cs b/src/Marten/EventStorage/Dialects/PostgresEventStoreDialect.cs index dfccc01d5f..249be18550 100644 --- a/src/Marten/EventStorage/Dialects/PostgresEventStoreDialect.cs +++ b/src/Marten/EventStorage/Dialects/PostgresEventStoreDialect.cs @@ -57,7 +57,7 @@ public RichEventStorageDescriptor BuildRichDescriptor(EventGraph graph, ISeriali appendEventSqlSuffix: ")", insertStreamSql: BuildInsertStreamSql(graph), updateStreamVersionSql: BuildUpdateStreamVersionSql(graph), - streamStateSelectSql: EventDocumentStorageGenerator.BuildStreamStateSelectSql(graph), + streamStateSelectSql: Marten.EventStorage.StreamStateSql.Build(graph), serializeEventData: e => serializer.ToJson(e.Data), metadataBinders: metadataBinders) { @@ -268,7 +268,7 @@ public QuickEventStorageDescriptor BuildQuickDescriptor(EventGraph graph, ISeria quickAppendEventsSql: BuildQuickAppendEventsSql(graph, serverTimestamps: false), insertStreamSql: BuildInsertStreamSql(graph), updateStreamVersionSql: BuildUpdateStreamVersionSql(graph), - streamStateSelectSql: EventDocumentStorageGenerator.BuildStreamStateSelectSql(graph), + streamStateSelectSql: Marten.EventStorage.StreamStateSql.Build(graph), serializeEventData: e => serializer.ToJson(e.Data)) { IsGuidStreamIdentity = isGuid, @@ -303,7 +303,7 @@ public QuickWithServerTimestampsEventStorageDescriptor BuildQuickWithServerTimes quickAppendEventsWithServerTimestampsSql: BuildQuickAppendEventsSql(graph, serverTimestamps: true), insertStreamSql: BuildInsertStreamSql(graph), updateStreamVersionSql: BuildUpdateStreamVersionSql(graph), - streamStateSelectSql: EventDocumentStorageGenerator.BuildStreamStateSelectSql(graph), + streamStateSelectSql: Marten.EventStorage.StreamStateSql.Build(graph), serializeEventData: e => serializer.ToJson(e.Data)) { IsGuidStreamIdentity = isGuid, diff --git a/src/Marten/EventStorage/StreamStateSql.cs b/src/Marten/EventStorage/StreamStateSql.cs new file mode 100644 index 0000000000..b736134d00 --- /dev/null +++ b/src/Marten/EventStorage/StreamStateSql.cs @@ -0,0 +1,18 @@ +#nullable enable +using Marten.Events; + +namespace Marten.EventStorage; + +/// +/// SQL fragment helpers shared between the closed-shape event-storage +/// adapter and the Postgres dialect SQL builders. Lives here (rather than +/// the per-mode descriptor classes) because the column ordering is coupled +/// to 's +/// implementation — +/// they must be edited together. +/// +internal static class StreamStateSql +{ + public static string Build(EventGraph graph) => + $"select id, version, type, timestamp, created, is_archived from {graph.DatabaseSchemaName}.mt_streams"; +} diff --git a/src/Marten/Events/CodeGeneration/CodeGenerationExtensions.cs b/src/Marten/Events/CodeGeneration/CodeGenerationExtensions.cs deleted file mode 100644 index 3fea115269..0000000000 --- a/src/Marten/Events/CodeGeneration/CodeGenerationExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using JasperFx.CodeGeneration; -using JasperFx.CodeGeneration.Frames; -using JasperFx.Core.Reflection; -using JasperFx.Events; -using Marten.Internal; -using Weasel.Postgresql; - -namespace Marten.Events.CodeGeneration; - -public static class CodeGenerationExtensions -{ - public static void AppendSql(this FramesCollection collection, string sql) - { - collection.Code($"{{0}}.{nameof(CommandBuilder.Append)}(\"{sql}\");", Use.Type()); - } - - public static void AppendSql(this FramesCollection collection, char sql) - { - collection.Code($"{{0}}.{nameof(CommandBuilder.Append)}('{sql}');", Use.Type()); - } -} diff --git a/src/Marten/Events/CodeGeneration/EventDocumentStorageGenerator.cs b/src/Marten/Events/CodeGeneration/EventDocumentStorageGenerator.cs deleted file mode 100644 index 7f168feaab..0000000000 --- a/src/Marten/Events/CodeGeneration/EventDocumentStorageGenerator.cs +++ /dev/null @@ -1,418 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using JasperFx.CodeGeneration; -using JasperFx.CodeGeneration.Frames; -using JasperFx.CodeGeneration.Model; -using JasperFx.Core; -using JasperFx.Core.Reflection; -using JasperFx.Events; -using JasperFx.RuntimeCompiler; -using Marten.Events.Archiving; -using Marten.Events.Operations; -using Marten.Events.Querying; -using Marten.Events.Schema; -using Marten.Internal; -using Marten.Internal.CodeGeneration; -using Marten.Schema; -using Marten.Storage; -using Marten.Storage.Metadata; -using Npgsql; -using Weasel.Postgresql; -using System.Diagnostics.CodeAnalysis; - -namespace Marten.Events.CodeGeneration; - -[UnconditionalSuppressMessage("Trimming", "IL2026", - Justification = "Class-level: consumes RUC-annotated members (ISerializer, JasperFx.Events aggregator graph, CloseAndBuildAs / GenericFactoryCache fallbacks, FastExpressionCompiler). Document/event/projection types flow in from StoreOptions / Schema.For() / projection registration and are preserved per the AOT publishing guide; AOT consumers supply a source-generator-backed serializer + pre-generated codegen artifacts.")] -[UnconditionalSuppressMessage("AOT", "IL3050", - Justification = "Class-level: uses Type.MakeGenericType / MethodInfo.MakeGenericMethod / Activator.CreateInstance / FastExpressionCompiler — runtime code generation. AOT consumers pre-generate codegen artifacts (codegen write) and supply source-generator-backed serializer impls per the AOT publishing guide.")] -internal static class EventDocumentStorageGenerator -{ - private const string StreamStateSelectorTypeName = "GeneratedStreamStateQueryHandler"; - private const string InsertStreamOperationName = "GeneratedInsertStream"; - private const string UpdateStreamVersionOperationName = "GeneratedStreamVersionOperation"; - internal const string EventDocumentStorageTypeName = "GeneratedEventDocumentStorage"; - - /// - /// Only for testing support - /// - /// - /// - public static EventDocumentStorage GenerateStorage(StoreOptions options) - { - var assembly = - new GeneratedAssembly(new GenerationRules(SchemaConstants.MartenGeneratedNamespace + ".EventStore")); - var builderType = AssembleTypes(options, assembly); - - - var compiler = new AssemblyGenerator(); - compiler.ReferenceAssembly(typeof(IMartenSession).Assembly); - compiler.Compile(assembly); - - Debug.WriteLine(builderType.SourceCode); - - return (EventDocumentStorage)Activator.CreateInstance(builderType.CompiledType!, options)!; - } - - public static GeneratedType AssembleTypes(StoreOptions options, GeneratedAssembly assembly) - { - assembly.ReferenceAssembly(typeof(EventGraph).Assembly); - - var builderType = assembly.AddType(EventDocumentStorageTypeName, typeof(EventDocumentStorage)); - - buildSelectorMethods(options, builderType); - - buildAppendEventOperations(options, assembly, builderType); - - - buildInsertStream(builderType, assembly, options.EventGraph); - - buildStreamQueryHandlerType(options.EventGraph, assembly); - - buildQueryForStreamMethod(options.EventGraph, builderType); - - buildUpdateStreamVersion(builderType, assembly, options.EventGraph); - - return builderType; - } - - private static void buildAppendEventOperations(StoreOptions options, GeneratedAssembly assembly, - GeneratedType builderType) - { - var appendEventOperationType = buildAppendEventOperation(options.EventGraph, assembly, AppendMode.Full); - builderType.MethodFor(nameof(EventDocumentStorage.AppendEvent)) - .Frames.ReturnNewGeneratedTypeObject(appendEventOperationType, "stream", "e"); - - var quickAppendEventGivenVersion = - buildAppendEventOperation(options.EventGraph, assembly, AppendMode.QuickWithVersion); - builderType.MethodFor(nameof(EventDocumentStorage.QuickAppendEventWithVersion)) - .Frames.ReturnNewGeneratedTypeObject(quickAppendEventGivenVersion, "stream", "e"); - - var quickAppend = buildQuickAppendOperation(options.EventGraph, assembly); - builderType.MethodFor(nameof(EventDocumentStorage.QuickAppendEvents)) - .Frames.ReturnNewGeneratedTypeObject(quickAppend, "stream"); - } - - private static void buildSelectorMethods(StoreOptions options, GeneratedType builderType) - { - var sync = builderType.MethodFor(nameof(EventDocumentStorage.ApplyReaderDataToEvent)); - var async = builderType.MethodFor(nameof(EventDocumentStorage.ApplyReaderDataToEventAsync)); - - // The json data column has to go first - var table = new EventsTable(options.EventGraph); - var columns = table.SelectColumns(); - - for (var i = 3; i < columns.Count; i++) - { - columns[i].GenerateSelectorCodeSync(sync, options.EventGraph, i); - columns[i].GenerateSelectorCodeAsync(async, options.EventGraph, i); - } - } - - private static GeneratedType buildUpdateStreamVersion(GeneratedType builderType, GeneratedAssembly assembly, - EventGraph graph) - { - var operationType = assembly.AddType(UpdateStreamVersionOperationName, typeof(UpdateStreamVersion)); - - var sql = $"update {graph.DatabaseSchemaName}.mt_streams "; - - var configureCommand = operationType.MethodFor("ConfigureCommand"); - configureCommand.DerivedVariables.Add( - new Variable(typeof(StreamAction), nameof(UpdateStreamVersion.Stream))); - - configureCommand.Frames.AppendSql(sql); - - configureCommand.Frames.Code($"var parameterBuilder = {{0}}.{nameof(CommandBuilder.CreateGroupedParameterBuilder)}();", Use.Type()); - - configureCommand.Frames.AppendSql("set version = "); - configureCommand.SetParameterFromMember(0, x => x.Version); - - configureCommand.Frames.AppendSql(" where id = "); - if (graph.StreamIdentity == StreamIdentity.AsGuid) - { - configureCommand.SetParameterFromMember(1, x => x.Id); - } - else - { - configureCommand.SetParameterFromMember(1, x => x.Key); - } - - configureCommand.Frames.AppendSql(" and version = "); - configureCommand.SetParameterFromMember(2, x => x.ExpectedVersionOnServer); - - if (graph.TenancyStyle == TenancyStyle.Conjoined) - { - configureCommand.Frames.AppendSql($" and {TenantIdColumn.Name} = "); - new TenantIdColumn().As().GenerateAppendCode(configureCommand, 3); - } - - configureCommand.Frames.AppendSql(" returning version"); - - builderType.MethodFor(nameof(EventDocumentStorage.UpdateStreamVersion)) - .Frames.Code($"return new {assembly.Namespace}.{UpdateStreamVersionOperationName}({{0}});", - Use.Type()); - - return operationType; - } - - private static void buildQueryForStreamMethod(EventGraph graph, GeneratedType builderType) - { - var arguments = new List - { - graph.StreamIdentity == StreamIdentity.AsGuid - ? $"stream.{nameof(StreamAction.Id)}" - : $"stream.{nameof(StreamAction.Key)}" - }; - - if (graph.TenancyStyle == TenancyStyle.Conjoined) - { - arguments.Add($"stream.{nameof(StreamAction.TenantId)}"); - } - - builderType.MethodFor(nameof(EventDocumentStorage.QueryForStream)) - .Frames.Code( - $"return new {builderType.ParentAssembly.Namespace}.{StreamStateSelectorTypeName}({arguments.Join(", ")});"); - } - - private static GeneratedType buildStreamQueryHandlerType(EventGraph graph, GeneratedAssembly assembly) - { - var streamQueryHandlerType = - assembly.AddType(StreamStateSelectorTypeName, typeof(StreamStateQueryHandler)); - - streamQueryHandlerType.AllInjectedFields.Add(graph.StreamIdentity == StreamIdentity.AsGuid - ? new InjectedField(typeof(Guid), "streamId") - : new InjectedField(typeof(string), "streamId")); - - buildConfigureCommandMethodForStreamState(graph, streamQueryHandlerType); - - // Row reading is no longer codegen'd here. The base StreamStateQueryHandler - // delegates Handle / HandleAsync to ISelector on IEventStorage, - // so the SELECT clause and the row read share a single definition site - // (EventDocumentStorage.StreamStateSelectSql + the explicit interface - // implementation alongside it). See JasperFx/marten#4347. - return streamQueryHandlerType; - } - - private static void buildConfigureCommandMethodForStreamState(EventGraph graph, - GeneratedType streamQueryHandlerType) - { - if (graph.TenancyStyle == TenancyStyle.Conjoined) - { - streamQueryHandlerType.AllInjectedFields.Add(new InjectedField(typeof(string), "tenantId")); - } - - var configureCommand = streamQueryHandlerType.MethodFor("ConfigureCommand"); - - // Pull the SELECT clause from the storage so the codegen and any other - // consumer (e.g. the IEventStore explorer surface) share one definition of - // "the column list that matches ISelector.Resolve". - var selectSql = BuildStreamStateSelectSql(graph); - configureCommand.Frames.AppendSql(selectSql + " where id = "); - - var idDbType = graph.StreamIdentity == StreamIdentity.AsGuid ? DbType.Guid : DbType.String; - configureCommand.Frames.Code($"var parameter1 = builder.{nameof(CommandBuilder.AppendParameter)}(_streamId);"); - configureCommand.Frames.Code("parameter1.DbType = {0};", idDbType); - - if (graph.TenancyStyle == TenancyStyle.Conjoined) - { - configureCommand.Frames.AppendSql($" and {TenantIdColumn.Name} = "); - configureCommand.Frames.Code($"var parameter2 = builder.{nameof(CommandBuilder.AppendParameter)}(_tenantId);"); - configureCommand.Frames.Code("parameter2.DbType = {0};", DbType.String); - } - } - - /// - /// Single source of truth for the StreamState SELECT clause. Kept as a static - /// helper alongside the codegen because the column ordering is coupled to - /// 's - /// implementation — they must be edited together. - /// - internal static string BuildStreamStateSelectSql(EventGraph graph) => - $"select id, version, type, timestamp, created, is_archived from {graph.DatabaseSchemaName}.mt_streams"; - - private static GeneratedType buildAppendEventOperation(EventGraph graph, GeneratedAssembly assembly, - AppendMode mode) - { - var typeName = "AppendEventOperation"; - if (mode != AppendMode.Full) - { - typeName += mode.ToString(); - } - - var baseType = typeof(AppendEventOperationBase); - var operationType = assembly.AddType(typeName, baseType); - - var configure = operationType.MethodFor(nameof(AppendEventOperationBase.ConfigureCommand)); - configure.DerivedVariables.Add(new Variable(typeof(IEvent), nameof(AppendEventOperationBase.Event))); - configure.DerivedVariables.Add(new Variable(typeof(StreamAction), nameof(AppendEventOperationBase.Stream))); - - var columns = new EventsTable(graph).SelectColumns() - - // Hokey, use an explicit model for writeable vs readable columns some day - .Where(x => x is not IsArchivedColumn).ToList(); - - // Hokey, but we need to move Sequence to the end - var sequence = columns.OfType().Single(); - columns.Remove(sequence); - columns.Add(sequence); - - var sql = - $"insert into {graph.DatabaseSchemaName}.mt_events ({columns.Select(x => x.Name).Join(", ")}) values ("; - - configure.Frames.AppendSql(sql); - - configure.Frames.Code($"var parameterBuilder = {{0}}.{nameof(CommandBuilder.CreateGroupedParameterBuilder)}(',');", Use.Type()); - - for (var i = 0; i < columns.Count; i++) - { - columns[i].GenerateAppendCode(configure, graph, i, mode); - var valueSql = columns[i].ValueSql(graph, mode); - if (valueSql != "?") - configure.Frames.AppendSql($"{(i > 0 ? "," : string.Empty)}{valueSql}"); - } - - configure.Frames.AppendSql(')'); - - return operationType; - } - - private static GeneratedType buildQuickAppendOperation(EventGraph graph, GeneratedAssembly assembly) - { - var operationType = assembly.AddType("QuickAppendEventsOperation", typeof(QuickAppendEventsOperationBase)); - - var table = new EventsTable(graph); - - var sql = $"select {graph.DatabaseSchemaName}.mt_quick_append_events("; - - var configure = operationType.MethodFor(nameof(QuickAppendEventsOperationBase.ConfigureCommand)); - configure.DerivedVariables.Add(new Variable(typeof(StreamAction), nameof(QuickAppendEventsOperationBase.Stream))); - - configure.Frames.AppendSql(sql); - - configure.Frames.Code($"var parameterBuilder = {{0}}.{nameof(CommandBuilder.CreateGroupedParameterBuilder)}(',');", Use.Type()); - - if (graph.StreamIdentity == StreamIdentity.AsGuid) - { - configure.Frames.Code("writeId(parameterBuilder);"); - } - else - { - configure.Frames.Code("writeKey(parameterBuilder);"); - } - - configure.Frames.Code("writeBasicParameters(parameterBuilder, session);"); - - if (table.Columns.OfType().Any()) - { - configure.Frames.Code("writeCausationIds(parameterBuilder);"); - } - - if (table.Columns.OfType().Any()) - { - configure.Frames.Code("writeCorrelationIds(parameterBuilder);"); - } - - if (table.Columns.OfType().Any()) - { - configure.Frames.Code("writeHeaders(parameterBuilder, session);"); - } - - if (table.Columns.OfType().Any()) - { - configure.Frames.Code("writeUserNames(parameterBuilder, session);"); - } - - if (graph.AppendMode == EventAppendMode.QuickWithServerTimestamps) - { - configure.Frames.Code("writeTimestamps(parameterBuilder);"); - } - - // HStore mode writes tags via a follow-up UPDATE; the function signature - // does not include the per-tag varchar[] parameters in that case. - if (graph.TagTypes.Count > 0 && graph.DcbStorageMode != DcbStorageMode.HStore) - { - configure.Frames.Code("writeAllTagValues(parameterBuilder);"); - } - - configure.Frames.AppendSql(')'); - - return operationType; - } - - private static GeneratedType buildInsertStream(GeneratedType builderType, GeneratedAssembly generatedAssembly, - EventGraph graph) - { - var operationType = generatedAssembly.AddType(InsertStreamOperationName, typeof(InsertStreamBase)); - - var columns = new StreamsTable(graph) - .Columns - .OfType() - .Where(x => x.Writes) - .ToArray(); - - var configureCommand = operationType.MethodFor("ConfigureCommand"); - configureCommand.DerivedVariables.Add(new Variable(typeof(StreamAction), nameof(InsertStreamBase.Stream))); - - // Strict identity enforcement: wrap the mt_streams insert in a modifying - // CTE so we can also INSERT into the non-partitioned mt_streams_identity - // tracking table in the same prepared statement. Postgres rejects - // semicolon-separated multi-statement prepared statements ("cannot insert - // multiple commands into a prepared statement"), so a single statement - // with a data-modifying CTE is the right shape here. - // PostgreSQL surfaces unique violations from the chained INSERT with - // TableName = "mt_streams_identity"; InsertStreamBase.matches() recognizes - // that name and translates it into ExistingStreamIdCollisionException. - if (graph.EnableStrictStreamIdentityEnforcement) - { - var identityColumnNames = graph.TenancyStyle == TenancyStyle.Conjoined - ? "tenant_id, id" - : "id"; - - var cteHead = - $"with new_stream as (insert into {graph.DatabaseSchemaName}.mt_streams ({columns.Select(x => x.Name).Join(", ")}) values ("; - configureCommand.Frames.AppendSql(cteHead); - } - else - { - var sql = - $"insert into {graph.DatabaseSchemaName}.mt_streams ({columns.Select(x => x.Name).Join(", ")}) values ("; - configureCommand.Frames.AppendSql(sql); - } - - configureCommand.Frames.Code($"var parameterBuilder = {{0}}.{nameof(CommandBuilder.CreateGroupedParameterBuilder)}(',');", Use.Type()); - - for (var i = 0; i < columns.Length; i++) - { - columns[i].GenerateAppendCode(configureCommand, i); - } - - if (graph.EnableStrictStreamIdentityEnforcement) - { - var identityColumnNames = graph.TenancyStyle == TenancyStyle.Conjoined - ? "tenant_id, id" - : "id"; - - // Close the CTE's INSERT, return the identity columns, then chain the - // identity-table INSERT off the CTE so it sees the same row that - // mt_streams just wrote. No new parameters needed — we're piping - // values through the CTE. - configureCommand.Frames.AppendSql( - $") returning {identityColumnNames}) " + - $"insert into {graph.DatabaseSchemaName}.{StreamIdentityEnforcementTable.TableName} ({identityColumnNames}) " + - $"select {identityColumnNames} from new_stream"); - } - else - { - configureCommand.Frames.AppendSql(')'); - } - - builderType.MethodFor(nameof(EventDocumentStorage.InsertStream)) - .Frames.ReturnNewGeneratedTypeObject(operationType, "stream"); - - return operationType; - } -} diff --git a/src/Marten/Events/EventDocumentStorage.cs b/src/Marten/Events/EventDocumentStorage.cs index b01d3ce5d9..b31f1ca8cc 100644 --- a/src/Marten/Events/EventDocumentStorage.cs +++ b/src/Marten/Events/EventDocumentStorage.cs @@ -230,7 +230,7 @@ public abstract IStorageOperation AppendEvent(EventGraph events, IMartenSession public abstract IQueryHandler QueryForStream(StreamAction stream); public abstract IStorageOperation UpdateStreamVersion(StreamAction stream); - public string StreamStateSelectSql => CodeGeneration.EventDocumentStorageGenerator.BuildStreamStateSelectSql(Events); + public string StreamStateSelectSql => Marten.EventStorage.StreamStateSql.Build(Events); StreamState ISelector.Resolve(DbDataReader reader) { diff --git a/src/Marten/Events/EventGraph.GeneratesCode.cs b/src/Marten/Events/EventGraph.GeneratesCode.cs index 32d11cc016..06b5ccb3eb 100644 --- a/src/Marten/Events/EventGraph.GeneratesCode.cs +++ b/src/Marten/Events/EventGraph.GeneratesCode.cs @@ -1,65 +1,35 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Threading.Tasks; using JasperFx.CodeGeneration; using JasperFx.Events; -using Marten.Events.CodeGeneration; -using Marten.Events.Projections; using Marten.Internal.Storage; using System.Diagnostics.CodeAnalysis; namespace Marten.Events; #nullable enable -[UnconditionalSuppressMessage("Trimming", "IL2072", - Justification = "Class-level: assigns the result of a reflective Type/MethodInfo lookup into a DAM-annotated target. Source types are preserved at the registration boundary.")] public partial class EventGraph: ICodeFile { - private readonly Type _storageType; - internal DocumentProvider? Provider { get; private set; } void ICodeFile.AssembleTypes(GeneratedAssembly assembly) { - // #4410 / W4: skip codegen when the closed-shape adapter handles - // the write path. Note: this only skips event-store codegen — - // document storage and projection codegen are unaffected. - if (UseClosedShapeStorage) return; - - EventDocumentStorageGenerator.AssembleTypes(Options, assembly); + // #4454 Phase 2: event storage is fully closed-shape — no Roslyn emit + // for the event-store write path. The ICodeFile contract is preserved + // for now so DocumentStore's ICodeFileCollection.BuildFiles aggregation + // still walks EventGraph; Phase 5 retires the contract entirely. } public bool AttachTypesSynchronously(GenerationRules rules, Assembly assembly, IServiceProvider? services, string containingNamespace) { - // #4410 / W4: when the closed-shape flag is on, skip the codegen - // lookup entirely and construct the hand-written - // ClosedShapeEventDocumentStorage adapter directly. The flag is - // off by default in v9; default flips in v10 and the codegen - // path is removed in v11. - if (UseClosedShapeStorage) - { - var closedShape = new Marten.EventStorage.ClosedShapeEventDocumentStorage(Options); - Provider = new DocumentProvider(null, closedShape, closedShape, closedShape, closedShape); - return true; - } - - var storageType = assembly.FindPreGeneratedType(containingNamespace, - EventDocumentStorageGenerator.EventDocumentStorageTypeName); - - if (storageType == null) - { - Provider = null; - } - else - { - var storage = (EventDocumentStorage)Activator.CreateInstance(storageType, Options)!; - Provider = new DocumentProvider(null, storage, storage, storage, storage); - } - - return Provider != null; + // Closed-shape adapter is constructed directly — no codegen lookup, + // no JasperFx.RuntimeCompiler fallback. ProviderGraph triggers this + // path on first IEvent storage request. + var closedShape = new Marten.EventStorage.ClosedShapeEventDocumentStorage(Options); + Provider = new DocumentProvider(null, closedShape, closedShape, closedShape, closedShape); + return true; } Task ICodeFile.AttachTypes(GenerationRules rules, Assembly assembly, IServiceProvider? services, diff --git a/src/Marten/Events/EventGraph.cs b/src/Marten/Events/EventGraph.cs index bd5556ca46..09c795a081 100644 --- a/src/Marten/Events/EventGraph.cs +++ b/src/Marten/Events/EventGraph.cs @@ -249,32 +249,6 @@ public override IEvent BuildEvent(object eventData) public bool EnableUniqueIndexOnEventId { get; set; } = false; - /// - /// Opt into the closed-shape event-storage hierarchy added in Marten 9 - /// (#4404 W4 / #4410). When , the event-store - /// path uses hand-written closed-shape - /// subclasses instead of the - /// Roslyn-emitted classes. Default in v9 — flag - /// flips to default-on in v10 and the codegen path is removed in v11. - /// - /// - /// - /// Three-mode coverage: Rich (Full + QuickWithVersion) / - /// Quick / QuickWithServerTimestamps. The mode is picked from - /// at DocumentStore construction; no - /// runtime branching on append mode after startup. - /// - /// - /// AOT publishing implication: when the flag is on, the event-store - /// path no longer needs at - /// runtime. Today's StoreOptions.AllowRuntimeCodeGeneration - /// gate still applies to the rest of the codegen surface (document - /// storage, projections); only the event-store slice migrates with - /// this flag. - /// - /// - public bool UseClosedShapeStorage { get; set; } = false; - private readonly List _ignoredIndexes = new(); public IReadOnlyList IgnoredIndexes => _ignoredIndexes; diff --git a/src/Marten/Events/IEventStoreOptions.cs b/src/Marten/Events/IEventStoreOptions.cs index bb226af0a9..e47be4001a 100644 --- a/src/Marten/Events/IEventStoreOptions.cs +++ b/src/Marten/Events/IEventStoreOptions.cs @@ -103,20 +103,6 @@ public interface IEventStoreOptions /// public bool EnableStrictStreamIdentityEnforcement { get; set; } - /// - /// Opt into the closed-shape event-storage hierarchy added in - /// Marten 9 (#4404 W4). When , the event-store - /// path uses hand-written closed-shape storage classes instead of - /// the runtime-emitted EventDocumentStorageGenerator output. - /// Default in v9 — the flag flips to - /// default-on in v10 and the codegen path is removed in v11. - /// - /// - /// AOT-publishing implication: when the flag is on, the event-store - /// slice no longer needs JasperFx.RuntimeCompiler at runtime. - /// - public bool UseClosedShapeStorage { get; set; } - /// /// Optional extension point to receive published messages as a side effect from /// aggregation projections