Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,22 @@
re-reads progression and succeeds when the replay had already reached the mark, plus a
configurable DaemonSettings.SideEffectGateTimeout), jasperfx#595 (BatchingChannel could
deliver its trailing batch twice on shutdown) and #597. Same lockstep rule as above. -->
<PackageVersion Include="JasperFx" Version="2.37.0" />
<PackageVersion Include="JasperFx.Events" Version="2.37.0" />
<PackageVersion Include="JasperFx" Version="2.37.1" />
<PackageVersion Include="JasperFx.Events" Version="2.37.1" />
<!--
The shared cross-store event sourcing compliance suites. Source-only package: the
suites compile inside Polecat.Tests so JasperFx's aggregate source generator binds
Polecat's own session types.
-->
<PackageVersion Include="JasperFx.Events.ComplianceTests" Version="2.37.1" />
<!-- Pin the sibling JasperFx packages for parity with Marten 9's matrix
even though Polecat doesn't currently reference them directly —
keeps the lockstep matrix coherent when transitive resolution
surfaces them through JasperFx / JasperFx.Events updates. The
RuntimeCompiler 5.x line is the active continuation of the 4.x
lineage; do not pin against the parallel stale 2.0.x series. -->
<PackageVersion Include="JasperFx.RuntimeCompiler" Version="5.0.0" />
<PackageVersion Include="JasperFx.SourceGenerator" Version="2.37.0" />
<PackageVersion Include="JasperFx.SourceGenerator" Version="2.37.1" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
Expand Down Expand Up @@ -159,7 +165,7 @@
<PackageVersion Include="StronglyTypedId" Version="1.0.0-beta08" />

<!-- Source generators (matched to JasperFx.Events above) -->
<PackageVersion Include="JasperFx.Events.SourceGenerator" Version="2.37.0" />
<PackageVersion Include="JasperFx.Events.SourceGenerator" Version="2.37.1" />

<!-- Build automation -->
<PackageVersion Include="Nuke.Common" Version="9.0.4" />
Expand Down
5 changes: 5 additions & 0 deletions src/Polecat.Tests/Compliance/ComplianceQuerySessionAlias.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// The shared compliance suites declare self-aggregating types whose EvolveAsync convention method
// takes the store's own read session. JasperFx's aggregate source generator resolves the parameter
// by type name, so a per-consumer global alias lets one shared source file bind to Polecat's
// IQuerySession here and to Marten's in Marten.
global using ComplianceQuerySession = Polecat.IQuerySession;
153 changes: 153 additions & 0 deletions src/Polecat.Tests/Compliance/PolecatComplianceFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using JasperFx;
using JasperFx.Events;
using JasperFx.Events.ComplianceTests;
using JasperFx.Events.Daemon;
using JasperFx.Events.Projections;
using JasperFx.Events.Tags;
using Microsoft.Data.SqlClient;
using Polecat.Batching;

namespace Polecat.Tests.Compliance;

/// <summary>
/// Polecat's implementation of the cross-store event sourcing compliance seam, closing it over
/// Polecat's <c>IEventStore&lt;IDocumentSession, IQuerySession&gt;</c> session pair.
/// </summary>
public class PolecatComplianceFixture : EventStoreComplianceFixture<IDocumentSession, IQuerySession>
{
private readonly List<object> _disposables = new();
private DocumentStore _store = null!;

public DocumentStore Store => _store;

protected override async Task BuildStoreAsync(ComplianceStoreConfig config)
{
var schemaName = (config.SchemaName ?? "compliance").ToLowerInvariant();

var options = new StoreOptions
{
ConnectionString = ConnectionSource.ConnectionString,
AutoCreateSchemaObjects = AutoCreate.All,
DatabaseSchemaName = schemaName,
UseNativeJsonType = ConnectionSource.SupportsNativeJson
};

config.ApplyTo(new PolecatComplianceRegistrar(options));

_store = new DocumentStore(options);
_disposables.Add(_store);

// Polecat applies schema changes explicitly rather than lazily -- one of the eight
// divergences the compliance seam exists to absorb.
await _store.Database.ApplyAllConfiguredChangesToDatabaseAsync().ConfigureAwait(false);
}

public override IDocumentSession OpenSession() => _store.LightweightSession();

public override Task SaveChangesAsync(IDocumentSession session, CancellationToken token)
=> session.SaveChangesAsync(token);

public override Task<T?> LoadDocumentAsync<T>(IQuerySession session, object id, CancellationToken token)
where T : class
=> id switch
{
Guid guidId => session.LoadAsync<T>(guidId, token),
int intId => session.LoadAsync<T>(intId, token),
long longId => session.LoadAsync<T>(longId, token),
string stringId => session.LoadAsync<T>(stringId, token),
_ => throw new ArgumentOutOfRangeException(nameof(id),
$"Polecat cannot load documents by an identity of type {id.GetType().FullName}")
};

public override IEventStoreOperations EventsFor(IDocumentSession session) => session.Events;

public override IComplianceBatch CreateBatch(IQuerySession session)
=> new PolecatComplianceBatch(session.CreateBatchQuery());

public override IEventRegistry Registry => _store.Options.EventGraph;

public override async Task CleanEventDataAsync()
{
await _store.Advanced.Clean.DeleteAllEventDataAsync().ConfigureAwait(false);
await _store.Advanced.Clean.DeleteAllDocumentsAsync().ConfigureAwait(false);
}

public override async Task<IProjectionDaemon> StartDaemonAsync()
{
var daemon = await _store.BuildProjectionDaemonAsync().ConfigureAwait(false);
_disposables.Add(daemon);

await daemon.StartAllAsync().ConfigureAwait(false);

return daemon;
}

public override Task WaitForNonStaleProjectionDataAsync(TimeSpan timeout)
=> _store.Database.WaitForNonStaleProjectionDataAsync(timeout);

/// <summary>
/// Polecat derives live aggregators automatically from self-aggregating types; there is no
/// explicit registration call to make.
/// </summary>
public override bool SupportsLiveAggregationRegistration => false;

public override async ValueTask DisposeAsync()
{
foreach (var disposable in _disposables)
{
switch (disposable)
{
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
break;
case IDisposable syncDisposable:
syncDisposable.Dispose();
break;
}
}

_disposables.Clear();
}

internal class PolecatComplianceRegistrar : IComplianceStoreRegistrar
{
private readonly StoreOptions _options;

public PolecatComplianceRegistrar(StoreOptions options)
{
_options = options;
}

// Straight to the registry: unlike Marten, Polecat's public EventStoreOptions facade has no
// AddEventType, so the shared IEventRegistry member on StoreOptions.EventGraph is the seam.
public void AddEventType(Type eventType) => _options.EventGraph.AddEventType(eventType);

public ITagTypeRegistration RegisterTagType<TTag>(string tableSuffix) where TTag : notnull
=> _options.Events.RegisterTagType<TTag>(tableSuffix);

public void Snapshot<TDoc>(SnapshotLifecycle lifecycle) where TDoc : notnull
=> _options.Projections.Snapshot<TDoc>(lifecycle);

// Live aggregators are derived automatically -- see SupportsLiveAggregationRegistration.
public void LiveAggregation<TDoc>() where TDoc : notnull
{
}
}

internal class PolecatComplianceBatch : IComplianceBatch
{
private readonly IBatchedQuery _batch;

public PolecatComplianceBatch(IBatchedQuery batch)
{
_batch = batch;
}

public Task<bool> EventsExist(EventTagQuery query) => _batch.EventsExist(query);

public Task<IEventBoundary<T>> FetchForWritingByTags<T>(EventTagQuery query) where T : class
=> _batch.FetchForWritingByTags<T>(query);

public Task Execute(CancellationToken token = default) => _batch.Execute(token);
}
}
23 changes: 23 additions & 0 deletions src/Polecat.Tests/Compliance/polecat_event_store_compliance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using JasperFx.Events.ComplianceTests;

namespace Polecat.Tests.Compliance;

/*
* Polecat's enrollment in the cross-store event sourcing compliance suites. Each class below is
* empty on purpose: the behavior lives once in JasperFx.Events.ComplianceTests and is closed here
* over Polecat's IEventStore<IDocumentSession, IQuerySession> session pair through
* PolecatComplianceFixture. Marten enrolls the same way, so drift between the two products' copies
* of these tests is no longer possible.
*/

public class self_aggregating_evolve_compliance
: SelfAggregatingEvolveCompliance<PolecatComplianceFixture, IDocumentSession, IQuerySession>;

public class dcb_tag_query_and_consistency_compliance
: DcbTagQueryAndConsistencyCompliance<PolecatComplianceFixture, IDocumentSession, IQuerySession>;

public class assign_tag_where_compliance
: AssignTagWhereCompliance<PolecatComplianceFixture, IDocumentSession, IQuerySession>;

public class async_daemon_compliance
: AsyncDaemonCompliance<PolecatComplianceFixture, IDocumentSession, IQuerySession>;
Loading
Loading