feat(batch13): port filestore all-last-seqs and filter-is-all helpers
This commit is contained in:
@@ -2300,6 +2300,92 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock should be held by caller.
|
||||||
|
private (ulong[] Seqs, Exception? Error) AllLastSeqsLocked()
|
||||||
|
{
|
||||||
|
if (_state.Msgs == 0 || NoTrackSubjects() || _psim == null)
|
||||||
|
return (Array.Empty<ulong>(), null);
|
||||||
|
|
||||||
|
var numSubjects = _psim.Size();
|
||||||
|
if (numSubjects == 0)
|
||||||
|
return (Array.Empty<ulong>(), null);
|
||||||
|
|
||||||
|
var seqs = new List<ulong>(numSubjects);
|
||||||
|
var seen = new HashSet<string>(numSubjects, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
for (var i = _blks.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (seen.Count == numSubjects)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var mb = _blks[i];
|
||||||
|
if (mb.Fss == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mb.Fss.IterFast((subjectBytes, ss) =>
|
||||||
|
{
|
||||||
|
var subject = Encoding.UTF8.GetString(subjectBytes);
|
||||||
|
if (!seen.Add(subject))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ss.LastNeedsUpdate)
|
||||||
|
RecalculateLastForSubject(subject, ss);
|
||||||
|
|
||||||
|
seqs.Add(ss.Last);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
seqs.Sort();
|
||||||
|
return (seqs.ToArray(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock should be held by caller.
|
||||||
|
private bool FilterIsAll(string[] filters)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(filters);
|
||||||
|
|
||||||
|
var streamSubjects = _cfg.Config.Subjects ?? [];
|
||||||
|
if (filters.Length != streamSubjects.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var sortedFilters = filters.ToArray();
|
||||||
|
var sortedSubjects = streamSubjects.ToArray();
|
||||||
|
Array.Sort(sortedFilters, StringComparer.Ordinal);
|
||||||
|
Array.Sort(sortedSubjects, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
for (var i = 0; i < sortedFilters.Length; i++)
|
||||||
|
{
|
||||||
|
if (!SubscriptionIndex.SubjectIsSubsetMatch(sortedSubjects[i], sortedFilters[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock should be held by caller.
|
||||||
|
private void RecalculateLastForSubject(string subject, SimpleState ss)
|
||||||
|
{
|
||||||
|
var subjectBytes = Encoding.UTF8.GetBytes(subject);
|
||||||
|
for (var i = _blks.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var fss = _blks[i].Fss;
|
||||||
|
if (fss == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var (candidate, ok) = fss.Find(subjectBytes);
|
||||||
|
if (!ok || candidate == null || candidate.Last == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ss.Last = candidate.Last;
|
||||||
|
ss.LastNeedsUpdate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.Last = 0;
|
||||||
|
ss.LastNeedsUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — type / state
|
// IStreamStore — type / state
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -309,6 +309,141 @@ public sealed class JetStreamFileStoreReadQueryTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllLastSeqsLocked_MultipleSubjects_ReturnsSortedLastSequences()
|
||||||
|
{
|
||||||
|
var storeDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(storeDir);
|
||||||
|
SetField(fs, "_state", new StreamState { Msgs = 6UL });
|
||||||
|
|
||||||
|
var b1 = NewBlock(1, 1, 20);
|
||||||
|
b1.Fss = BuildSubjectStateTree(("foo.a", 2, 10, 10), ("foo.b", 2, 20, 20));
|
||||||
|
var b2 = NewBlock(2, 21, 40);
|
||||||
|
b2.Fss = BuildSubjectStateTree(("foo.b", 1, 25, 25), ("foo.c", 1, 15, 15));
|
||||||
|
|
||||||
|
ConfigureBlocks(fs, b1, b2);
|
||||||
|
SetPsims(fs, ("foo.a", 1, 1, 2), ("foo.b", 1, 2, 3), ("foo.c", 2, 2, 1));
|
||||||
|
|
||||||
|
var (seqs, error) = InvokeAllLastSeqsLocked(fs);
|
||||||
|
|
||||||
|
error.ShouldBeNull();
|
||||||
|
seqs.ShouldBe([10UL, 15UL, 25UL]);
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(storeDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllLastSeqsLocked_NoMessagesOrNoTracking_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var storeDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(storeDir);
|
||||||
|
SetField(fs, "_state", new StreamState { Msgs = 0UL });
|
||||||
|
SetPsims(fs, ("foo.a", 1, 1, 1));
|
||||||
|
|
||||||
|
var (noMsgsSeqs, noMsgsError) = InvokeAllLastSeqsLocked(fs);
|
||||||
|
noMsgsError.ShouldBeNull();
|
||||||
|
noMsgsSeqs.ShouldBeEmpty();
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(storeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var noTrackDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(noTrackDir, subjects: []);
|
||||||
|
SetField(fs, "_state", new StreamState { Msgs = 1UL });
|
||||||
|
SetField(fs, "_psim", new SubjectTree<Psi>());
|
||||||
|
|
||||||
|
var (noTrackSeqs, noTrackError) = InvokeAllLastSeqsLocked(fs);
|
||||||
|
noTrackError.ShouldBeNull();
|
||||||
|
noTrackSeqs.ShouldBeEmpty();
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(noTrackDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllLastSeqsLocked_LastNeedsUpdate_RecalculatesBeforeCollecting()
|
||||||
|
{
|
||||||
|
var storeDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(storeDir);
|
||||||
|
SetField(fs, "_state", new StreamState { Msgs = 3UL });
|
||||||
|
SetPsims(fs, ("foo.a", 1, 2, 3));
|
||||||
|
|
||||||
|
var b1 = NewBlock(1, 1, 12);
|
||||||
|
b1.Fss = BuildSubjectStateTree(("foo.a", 3, 1, 12));
|
||||||
|
|
||||||
|
var b2 = NewBlock(2, 13, 20);
|
||||||
|
var stale = new SimpleState { Msgs = 3UL, First = 1UL, Last = 0UL, LastNeedsUpdate = true };
|
||||||
|
var staleTree = new SubjectTree<SimpleState>();
|
||||||
|
staleTree.Insert(System.Text.Encoding.UTF8.GetBytes("foo.a"), stale);
|
||||||
|
b2.Fss = staleTree;
|
||||||
|
|
||||||
|
ConfigureBlocks(fs, b1, b2);
|
||||||
|
|
||||||
|
var (seqs, error) = InvokeAllLastSeqsLocked(fs);
|
||||||
|
|
||||||
|
error.ShouldBeNull();
|
||||||
|
seqs.ShouldBe([12UL]);
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(storeDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterIsAll_ReorderedEquivalentFilters_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var storeDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(storeDir, ["foo.*", "bar.>"]);
|
||||||
|
|
||||||
|
InvokeFilterIsAll(fs, ["bar.>", "foo.*"]).ShouldBeTrue();
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(storeDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterIsAll_CountMismatchOrNonSubset_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var storeDir = CreateStoreDir();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = CreateStore(storeDir, ["foo.*", "bar.>"]);
|
||||||
|
|
||||||
|
InvokeFilterIsAll(fs, ["foo.A"]).ShouldBeFalse();
|
||||||
|
InvokeFilterIsAll(fs, ["bar.>", "baz.*"]).ShouldBeFalse();
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteStoreDir(storeDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string CreateStoreDir()
|
private static string CreateStoreDir()
|
||||||
{
|
{
|
||||||
var root = Path.Combine(Path.GetTempPath(), $"fs-read-query-{Guid.NewGuid():N}");
|
var root = Path.Combine(Path.GetTempPath(), $"fs-read-query-{Guid.NewGuid():N}");
|
||||||
@@ -322,8 +457,10 @@ public sealed class JetStreamFileStoreReadQueryTests
|
|||||||
Directory.Delete(storeDir, recursive: true);
|
Directory.Delete(storeDir, recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JetStreamFileStore CreateStore(string storeDir)
|
private static JetStreamFileStore CreateStore(string storeDir, string[]? subjects = null)
|
||||||
{
|
{
|
||||||
|
subjects ??= ["foo.*", "bar.*", "zoo.*"];
|
||||||
|
|
||||||
return new JetStreamFileStore(
|
return new JetStreamFileStore(
|
||||||
new FileStoreConfig { StoreDir = storeDir, BlockSize = 1024 },
|
new FileStoreConfig { StoreDir = storeDir, BlockSize = 1024 },
|
||||||
new FileStreamInfo
|
new FileStreamInfo
|
||||||
@@ -333,7 +470,7 @@ public sealed class JetStreamFileStoreReadQueryTests
|
|||||||
{
|
{
|
||||||
Name = "S",
|
Name = "S",
|
||||||
Storage = StorageType.FileStorage,
|
Storage = StorageType.FileStorage,
|
||||||
Subjects = ["foo.*", "bar.*", "zoo.*"],
|
Subjects = subjects,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -434,6 +571,24 @@ public sealed class JetStreamFileStoreReadQueryTests
|
|||||||
_ = mi!.Invoke(fs, [filter, ss]);
|
_ = mi!.Invoke(fs, [filter, ss]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (ulong[] Seqs, Exception? Error) InvokeAllLastSeqsLocked(JetStreamFileStore fs)
|
||||||
|
{
|
||||||
|
var mi = typeof(JetStreamFileStore).GetMethod("AllLastSeqsLocked", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
mi.ShouldNotBeNull();
|
||||||
|
var result = mi!.Invoke(fs, []);
|
||||||
|
result.ShouldNotBeNull();
|
||||||
|
return ((ulong[], Exception?))result!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InvokeFilterIsAll(JetStreamFileStore fs, string[] filters)
|
||||||
|
{
|
||||||
|
var mi = typeof(JetStreamFileStore).GetMethod("FilterIsAll", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
mi.ShouldNotBeNull();
|
||||||
|
var result = mi!.Invoke(fs, [filters]);
|
||||||
|
result.ShouldNotBeNull();
|
||||||
|
return (bool)result;
|
||||||
|
}
|
||||||
|
|
||||||
private static Psi? GetPsi(JetStreamFileStore fs, string subject)
|
private static Psi? GetPsi(JetStreamFileStore fs, string subject)
|
||||||
{
|
{
|
||||||
var fi = typeof(JetStreamFileStore).GetField("_psim", BindingFlags.Instance | BindingFlags.NonPublic);
|
var fi = typeof(JetStreamFileStore).GetField("_psim", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|||||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user