feat(batch13): port filestore filtered-pending query helpers

This commit is contained in:
Joseph Doherty
2026-02-28 14:45:14 -05:00
parent 430ba17f42
commit 3d2638dfaa
4 changed files with 365 additions and 1 deletions

View File

@@ -154,6 +154,161 @@ public sealed class JetStreamFileStoreReadQueryTests
}
}
[Theory]
[InlineData("")]
[InlineData(">")]
public void NumFilteredPending_FilterIsAll_UsesStreamState(string filter)
{
var storeDir = CreateStoreDir();
try
{
var fs = CreateStore(storeDir);
SetField(fs, "_state", new StreamState { FirstSeq = 5UL, LastSeq = 99UL, Msgs = 42UL });
var ss = new SimpleState();
InvokeNumFilteredPending(fs, filter, ss);
ss.Msgs.ShouldBe(42UL);
ss.First.ShouldBe(5UL);
ss.Last.ShouldBe(99UL);
fs.Stop();
}
finally
{
DeleteStoreDir(storeDir);
}
}
[Fact]
public void NumFilteredPending_NoMatch_ResetsSimpleStateToZero()
{
var storeDir = CreateStoreDir();
try
{
var fs = CreateStore(storeDir);
SetPsims(fs, ("foo.a", 1, 1, 1));
var ss = new SimpleState { Msgs = 999UL, First = 123UL, Last = 456UL };
InvokeNumFilteredPending(fs, "bar.b", ss);
ss.Msgs.ShouldBe(0UL);
ss.First.ShouldBe(0UL);
ss.Last.ShouldBe(0UL);
fs.Stop();
}
finally
{
DeleteStoreDir(storeDir);
}
}
[Fact]
public void NumFilteredPending_LiteralAndWildcard_UsesPsiTotalsAndBlockBounds()
{
var storeDir = CreateStoreDir();
try
{
var fs = CreateStore(storeDir);
var b1 = NewBlock(1, 1, 20);
b1.Fss = BuildSubjectStateTree(("foo.a", 2, 10, 11));
var b2 = NewBlock(2, 21, 40);
b2.Fss = BuildSubjectStateTree(("foo.a", 1, 30, 30), ("foo.b", 3, 35, 40));
ConfigureBlocks(fs, b1, b2);
SetPsims(fs, ("foo.a", 1, 2, 3), ("foo.b", 2, 2, 3));
var literal = new SimpleState();
InvokeNumFilteredPending(fs, "foo.a", literal);
literal.Msgs.ShouldBe(3UL);
literal.First.ShouldBe(10UL);
literal.Last.ShouldBe(30UL);
var wildcard = new SimpleState();
InvokeNumFilteredPending(fs, "foo.*", wildcard);
wildcard.Msgs.ShouldBe(6UL);
wildcard.First.ShouldBe(10UL);
wildcard.Last.ShouldBe(40UL);
fs.Stop();
}
finally
{
DeleteStoreDir(storeDir);
}
}
[Fact]
public void NumFilteredPendingNoLast_LiteralMatch_LeavesLastAsZero()
{
var storeDir = CreateStoreDir();
try
{
var fs = CreateStore(storeDir);
var b1 = NewBlock(1, 1, 20);
b1.Fss = BuildSubjectStateTree(("foo.a", 2, 10, 11));
ConfigureBlocks(fs, b1);
SetPsims(fs, ("foo.a", 1, 1, 2));
var ss = new SimpleState();
InvokeNumFilteredPendingNoLast(fs, "foo.a", ss);
ss.Msgs.ShouldBe(2UL);
ss.First.ShouldBe(10UL);
ss.Last.ShouldBe(0UL);
fs.Stop();
}
finally
{
DeleteStoreDir(storeDir);
}
}
[Fact]
public void NumFilteredPending_StaleFirstBlockHint_UpdatesPsiFirstBlock()
{
var storeDir = CreateStoreDir();
try
{
var fs = CreateStore(storeDir);
var b1 = NewBlock(1, 1, 10);
b1.Fss = BuildSubjectStateTree(("bar.a", 1, 1, 1));
var b2 = NewBlock(2, 11, 20);
b2.Fss = BuildSubjectStateTree(("foo.a", 1, 20, 20));
ConfigureBlocks(fs, b1, b2);
SetPsims(fs, ("foo.a", 1, 2, 1));
var ss = new SimpleState();
InvokeNumFilteredPending(fs, "foo.a", ss);
ss.Msgs.ShouldBe(1UL);
ss.First.ShouldBe(20UL);
ss.Last.ShouldBe(20UL);
var updated = false;
for (var i = 0; i < 100; i++)
{
var psi = GetPsi(fs, "foo.a");
if (psi != null && psi.Fblk == 2)
{
updated = true;
break;
}
Thread.Sleep(10);
}
updated.ShouldBeTrue();
fs.Stop();
}
finally
{
DeleteStoreDir(storeDir);
}
}
private static string CreateStoreDir()
{
var root = Path.Combine(Path.GetTempPath(), $"fs-read-query-{Guid.NewGuid():N}");
@@ -193,6 +348,24 @@ public sealed class JetStreamFileStoreReadQueryTests
};
}
private static SubjectTree<SimpleState> BuildSubjectStateTree(params (string Subject, ulong Msgs, ulong First, ulong Last)[] states)
{
var tree = new SubjectTree<SimpleState>();
foreach (var (subject, msgs, first, last) in states)
{
tree.Insert(
System.Text.Encoding.UTF8.GetBytes(subject),
new SimpleState
{
Msgs = msgs,
First = first,
Last = last,
});
}
return tree;
}
private static void ConfigureBlocks(JetStreamFileStore fs, params MessageBlock[] blocks)
{
var ordered = blocks.OrderBy(b => b.Index).ToList();
@@ -247,6 +420,29 @@ public sealed class JetStreamFileStoreReadQueryTests
return ((int, Exception?))result!;
}
private static void InvokeNumFilteredPending(JetStreamFileStore fs, string filter, SimpleState ss)
{
var mi = typeof(JetStreamFileStore).GetMethod("NumFilteredPending", BindingFlags.Instance | BindingFlags.NonPublic);
mi.ShouldNotBeNull();
_ = mi!.Invoke(fs, [filter, ss]);
}
private static void InvokeNumFilteredPendingNoLast(JetStreamFileStore fs, string filter, SimpleState ss)
{
var mi = typeof(JetStreamFileStore).GetMethod("NumFilteredPendingNoLast", BindingFlags.Instance | BindingFlags.NonPublic);
mi.ShouldNotBeNull();
_ = mi!.Invoke(fs, [filter, ss]);
}
private static Psi? GetPsi(JetStreamFileStore fs, string subject)
{
var fi = typeof(JetStreamFileStore).GetField("_psim", BindingFlags.Instance | BindingFlags.NonPublic);
fi.ShouldNotBeNull();
var psim = fi!.GetValue(fs).ShouldBeOfType<SubjectTree<Psi>>();
var (psi, found) = psim.Find(System.Text.Encoding.UTF8.GetBytes(subject));
return found ? psi : null;
}
private static void SetField<T>(object target, string fieldName, T value)
{
var fi = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);