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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<JasperFxVersion>2.28.1</JasperFxVersion>
<JasperFxVersion>2.29.0</JasperFxVersion>
<LangVersion>13</LangVersion>
<NoWarn>1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618</NoWarn>
<Authors>Jeremy D. Miller;Jaedyn Tonee</Authors>
Expand Down
31 changes: 31 additions & 0 deletions src/EventTests/Daemon/ReadProjectionProgressDefaultsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ private sealed class ProgressionEventDatabase : BareDatabaseBase
? new ValueTask<ProjectionProgressRow?>(
new ProjectionProgressRow("Orders", tenantId, 42, "Running", null))
: new ValueTask<ProjectionProgressRow?>((ProjectionProgressRow?)null);

public override ValueTask<ProjectionProgressRow?> ReadProjectionProgressAsync(
ShardName name, CancellationToken token)
=> name.Identity == "Orders:All"
? new ValueTask<ProjectionProgressRow?>(
new ProjectionProgressRow("Orders", null, 42, null, null))
: new ValueTask<ProjectionProgressRow?>((ProjectionProgressRow?)null);
}

// Split out so the overriding stub does not have to restate every unrelated member.
Expand All @@ -68,6 +75,10 @@ private abstract class BareDatabaseBase : IEventDatabase
string projectionName, string? tenantId, CancellationToken token)
=> throw new NotImplementedException();

public virtual ValueTask<ProjectionProgressRow?> ReadProjectionProgressAsync(
ShardName name, CancellationToken token)
=> throw new NotImplementedException();

public string Identifier => throw new NotImplementedException();
public Uri DatabaseUri => throw new NotImplementedException();
public ShardStateTracker Tracker => throw new NotImplementedException();
Expand Down Expand Up @@ -135,6 +146,26 @@ public async Task an_implementing_store_returns_null_when_no_row_exists_for_the_
row.ShouldBeNull();
}

[Fact]
public async Task default_throws_for_the_shard_name_overload_too()
{
// The exact ShardName overload ships as a default interface member for the same reason: an
// unimplemented store must surface "not implemented", never borrow the "no row" null.
await Should.ThrowAsync<NotSupportedException>(async () =>
await theBareDatabase.ReadProjectionProgressAsync(ShardName.Compose("Orders"), CancellationToken.None));
}

[Fact]
public async Task an_implementing_store_can_return_a_row_by_shard_name()
{
var row = await theProgressionDatabase.ReadProjectionProgressAsync(
ShardName.Compose("Orders"), CancellationToken.None);

row.ShouldNotBeNull();
row.ProjectionName.ShouldBe("Orders");
row.Sequence.ShouldBe(42);
}

[Fact]
public void null_tenant_id_means_store_global_or_default_tenant()
{
Expand Down
24 changes: 24 additions & 0 deletions src/JasperFx.Events/IEventDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,28 @@ Task<IReadOnlyList<DeadLetterShardCount>> FetchDeadLetterCountsAsync(string? ten
CancellationToken token)
=> throw new NotSupportedException(
"ReadProjectionProgressAsync is not implemented on this IEventDatabase. Use an event store (Marten or Polecat) that supports reading a single projection progression row.");

/// <summary>
/// Exact per-cell progression read: look up the single progression row whose stored identity equals
/// <paramref name="name" />'s <see cref="ShardName.Identity" />. Unlike the
/// <see cref="ReadProjectionProgressAsync(string,string?,CancellationToken)" /> overload there is no
/// version/shard collapsing — the caller supplies the full <see cref="ShardName" /> it already holds
/// (for example a cell from <see cref="AllProjectionProgress(CancellationToken)" />), so a blue/green
/// deploy's versions, a sliced projection's shard keys, and per-tenant partitions each address their
/// own row unambiguously. A <see cref="ShardName.ShardKey" /> of <c>All</c> is the projection's global
/// cell, matching the store's "All means the whole projection" convention. Returns null when no row
/// exists for that identity yet.
/// <para>
/// Like the other overload the default implementation throws rather than returning null: null is the
/// meaningful "no row yet" answer and must not be borrowed by a store that has not implemented this.
/// Event stores (Marten, Polecat) override it against their progression table. See jasperfx#435.
/// </para>
/// </summary>
/// <param name="name">Full shard identity of the cell to read.</param>
/// <param name="token"></param>
ValueTask<ProjectionProgressRow?> ReadProjectionProgressAsync(
ShardName name,
CancellationToken token)
=> throw new NotSupportedException(
"ReadProjectionProgressAsync is not implemented on this IEventDatabase. Use an event store (Marten or Polecat) that supports reading a single projection progression row.");
}
Loading