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
12 changes: 7 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,16 @@
JasperFx 2.28.1: jasperfx#528 (marten#4966) — natural-key discovery now builds an event
mapping for a two-arg static evolve method (static TDoc Apply(TEvent e, TDoc current)), so a
natural key that CHANGES via an update event is written to mt_natural_key_X on live append and
rebuild; previously only the create event's key was ever recorded. -->
<PackageVersion Include="JasperFx" Version="2.28.1" />
<PackageVersion Include="JasperFx.Events" Version="2.28.1" />
<PackageVersion Include="JasperFx.Events.SourceGenerator" Version="2.28.1">
rebuild; previously only the create event's key was ever recorded.
JasperFx 2.29.0: jasperfx#529 (marten#4975) — exact ReadProjectionProgressAsync(ShardName) overload
on IEventDatabase (no version/shard collapsing), adopted by MartenDatabase here. -->
<PackageVersion Include="JasperFx" Version="2.29.0" />
<PackageVersion Include="JasperFx.Events" Version="2.29.0" />
<PackageVersion Include="JasperFx.Events.SourceGenerator" Version="2.29.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="JasperFx.SourceGenerator" Version="2.28.1" />
<PackageVersion Include="JasperFx.SourceGenerator" Version="2.29.0" />
<PackageVersion Include="Jil" Version="3.0.0-alpha2" />
<PackageVersion Include="Lamar" Version="7.1.1" />
<PackageVersion Include="Lamar.Microsoft.DependencyInjection" Version="15.0.0" />
Expand Down
43 changes: 43 additions & 0 deletions src/DaemonTests/Internals/read_projection_progress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ private async Task seedProgression(params (ShardName name, long sequence)[] rows
((IEventDatabase)theStore.Tenancy.Default.Database)
.ReadProjectionProgressAsync(projectionName, tenantId, CancellationToken.None);

private ValueTask<ProjectionProgressRow?> read(ShardName name) =>
((IEventDatabase)theStore.Tenancy.Default.Database)
.ReadProjectionProgressAsync(name, CancellationToken.None);

[Fact]
public async Task reads_the_store_global_row()
{
Expand Down Expand Up @@ -103,4 +107,43 @@ await seedProgression(
(await read("Orders", null))!.Sequence.ShouldBe(10);
(await read("OrdersHistory", null))!.Sequence.ShouldBe(5);
}

// #4975 / jasperfx#529 — the exact ShardName overload does NO collapsing.
[Fact]
public async Task exact_shard_name_reads_the_specific_version_not_the_newest()
{
await seedProgression(
(ShardName.Compose("Orders", version: 2), 25),
(ShardName.Compose("Orders", version: 3), 40));

var v2 = await read(ShardName.Compose("Orders", version: 2));

v2.ShouldNotBeNull();
v2.Sequence.ShouldBe(25); // the exact V2 row, not the newest V3
v2.ProjectionName.ShouldBe("Orders");
v2.AgentStatus.ShouldBeNull();
v2.LastHeartbeat.ShouldBeNull();
}

[Fact]
public async Task exact_shard_name_returns_null_when_the_identity_does_not_exist()
{
await seedProgression((ShardName.Compose("Orders"), 10));

(await read(ShardName.Compose("Orders", version: 9))).ShouldBeNull();
}

[Fact]
public async Task exact_shard_name_distinguishes_tenant_rows()
{
await seedProgression(
(ShardName.Compose("Orders"), 10),
(ShardName.Compose("Orders", tenantId: "tenant1"), 99));

(await read(ShardName.Compose("Orders")))!.Sequence.ShouldBe(10);

var tenantRow = await read(ShardName.Compose("Orders", tenantId: "tenant1"));
tenantRow!.Sequence.ShouldBe(99);
tenantRow.TenantId.ShouldBe("tenant1");
}
}
40 changes: 40 additions & 0 deletions src/Marten/Storage/MartenDatabase.EventStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,46 @@ public async Task<IReadOnlyList<ShardState>> AllProjectionProgress(string? tenan
}
}

/// <summary>
/// #4975 / jasperfx#529 — exact per-cell progression read. Unlike the
/// <see cref="ReadProjectionProgressAsync(string,string?,CancellationToken)"/> overload this does no
/// version/shard collapsing: it looks up the single <c>mt_event_progression</c> row whose <c>name</c>
/// equals <paramref name="name"/>'s <see cref="ShardName.Identity"/> verbatim, so a blue/green deploy's
/// versions, a sliced projection's shard keys, and per-tenant partitions each address their own row.
/// A <see cref="ShardName.ShardKey"/> of <c>All</c> is the projection's global cell. Returns null when no
/// row exists for that identity; <see cref="ProjectionProgressRow.AgentStatus"/> and
/// <see cref="ProjectionProgressRow.LastHeartbeat"/> stay null (jasperfx#519).
/// </summary>
public async ValueTask<ProjectionProgressRow?> ReadProjectionProgressAsync(
ShardName name, CancellationToken token)
{
await EnsureStorageExistsAsync(typeof(IEvent), token).ConfigureAwait(false);

await using var conn = CreateConnection();
try
{
await conn.OpenAsync(token).ConfigureAwait(false);

var builder = new CommandBuilder();
builder.Append(
$"select last_seq_id from {Options.EventGraph.DatabaseSchemaName}.mt_event_progression where name = ");
builder.AppendParameter(name.Identity);

await using var reader = await conn.ExecuteReaderAsync(builder, token).ConfigureAwait(false);
if (!await reader.ReadAsync(token).ConfigureAwait(false))
{
return null;
}

var sequence = await reader.GetFieldValueAsync<long>(0, token).ConfigureAwait(false);
return new ProjectionProgressRow(name.Name, name.TenantId, sequence, null, null);
}
finally
{
await conn.CloseAsync().ConfigureAwait(false);
}
}

/// <summary>
/// #4785 / jasperfx#473 — Marten override of the store-agnostic
/// <see cref="IEventDatabase.DeleteProjectionProgressByShardNameAsync"/>
Expand Down
Loading