feat(storage): add tombstone tracking and purge operations (Go parity)
Implement PurgeEx, Compact, Truncate, FilteredState, SubjectsState, SubjectsTotals, State, FastState, GetSeqFromTime on FileStore. Add MsgBlock.IsDeleted, DeletedSequences, EnumerateNonDeleted. Includes wildcard subject support via SubjectMatch for all filtered operations.
This commit is contained in:
@@ -322,6 +322,69 @@ public sealed class MsgBlock : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given sequence number has been soft-deleted in this block.
|
||||
/// Reference: golang/nats-server/server/filestore.go — dmap (deleted map) lookup.
|
||||
/// </summary>
|
||||
public bool IsDeleted(ulong sequence)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try { return _deleted.Contains(sequence); }
|
||||
finally { _lock.ExitReadLock(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the set of soft-deleted sequence numbers for read-only inspection.
|
||||
/// Reference: golang/nats-server/server/filestore.go — dmap access for state queries.
|
||||
/// </summary>
|
||||
public IReadOnlySet<ulong> DeletedSequences
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try { return new HashSet<ulong>(_deleted); }
|
||||
finally { _lock.ExitReadLock(); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all non-deleted sequences in this block along with their subjects.
|
||||
/// Used by FileStore for subject-filtered operations (PurgeEx, SubjectsState, etc.).
|
||||
/// Reference: golang/nats-server/server/filestore.go — loadBlock, iterating non-deleted records.
|
||||
/// </summary>
|
||||
public IEnumerable<(ulong Sequence, string Subject)> EnumerateNonDeleted()
|
||||
{
|
||||
// Snapshot index and deleted set under the read lock, then decode outside it.
|
||||
List<(long Offset, int Length, ulong Seq)> entries;
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
entries = new List<(long, int, ulong)>(_index.Count);
|
||||
foreach (var (seq, (offset, length)) in _index)
|
||||
{
|
||||
if (!_deleted.Contains(seq))
|
||||
entries.Add((offset, length, seq));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
|
||||
// Sort by sequence for deterministic output.
|
||||
entries.Sort((a, b) => a.Seq.CompareTo(b.Seq));
|
||||
|
||||
foreach (var (offset, length, seq) in entries)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
RandomAccess.Read(_handle, buffer, offset);
|
||||
var record = MessageRecord.Decode(buffer);
|
||||
if (record is not null && !record.Deleted)
|
||||
yield return (record.Sequence, record.Subject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any buffered writes to disk.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user