diff --git a/src/NATS.Client.KeyValueStore/INatsKVStore.cs b/src/NATS.Client.KeyValueStore/INatsKVStore.cs index 58b8a48ee..a1ed8ce37 100644 --- a/src/NATS.Client.KeyValueStore/INatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/INatsKVStore.cs @@ -138,4 +138,14 @@ public interface INatsKVStore /// An async enumerable of keys to be used in an await foreach /// There was a conflict in options, e.g. IncludeHistory and UpdatesOnly are only valid when ResumeAtRevision is not set. IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = default, CancellationToken cancellationToken = default); + + /// + /// Get a filtered set of keys in the bucket + /// + /// Subject-based wildcard filters to filter on + /// Watch options + /// A used to cancel the API call. + /// An async enumerable of keys to be used in an await foreach + /// There was a conflict in options, e.g. IncludeHistory and UpdatesOnly are only valid when ResumeAtRevision is not set. + IAsyncEnumerable GetKeysAsync(IEnumerable filters, NatsKVWatchOpts? opts = default, CancellationToken cancellationToken = default); } diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index 30540b436..cbc8acafa 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -438,14 +438,18 @@ public async ValueTask PurgeDeletesAsync(NatsKVPurgeOpts? opts = default, Cancel } /// - public async IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = default, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = default, CancellationToken cancellationToken = default) + => GetKeysAsync([">"], opts, cancellationToken); + + /// + public async IAsyncEnumerable GetKeysAsync(IEnumerable filters, NatsKVWatchOpts? opts = default, [EnumeratorCancellation] CancellationToken cancellationToken = default) { opts ??= NatsKVWatchOpts.Default; opts = opts with { IgnoreDeletes = false, MetaOnly = true, UpdatesOnly = false, }; // Type doesn't matter here, we're just using the watcher to get the keys - await using var watcher = await WatchInternalAsync([">"], serializer: default, opts, cancellationToken); + await using var watcher = await WatchInternalAsync(filters, serializer: default, opts, cancellationToken); if (watcher.InitialConsumer.Info.NumPending == 0) yield break; diff --git a/tests/NATS.Client.KeyValueStore.Tests/GetKeysTest.cs b/tests/NATS.Client.KeyValueStore.Tests/GetKeysTest.cs index 38755b4e7..41fef6333 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/GetKeysTest.cs +++ b/tests/NATS.Client.KeyValueStore.Tests/GetKeysTest.cs @@ -80,4 +80,40 @@ public async Task Get_keys_when_empty() Assert.Equal(3, count); } + + [SkipIfNatsServer(versionEarlierThan: "2.10")] + public async Task Get_filtered_keys() + { + const string bucket = "b1"; + var config = new NatsKVConfig(bucket); + + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var cancellationToken = cts.Token; + + await using var server = NatsServer.StartJS(); + await using var nats1 = server.CreateClientConnection(); + var js1 = new NatsJSContext(nats1); + var kv1 = new NatsKVContext(js1); + var store1 = await kv1.CreateStoreAsync(config, cancellationToken: cancellationToken); + + await store1.PutAsync("a.1", 1, cancellationToken: cancellationToken); + await store1.PutAsync("a.2", 2, cancellationToken: cancellationToken); + await store1.PutAsync("b.1", 1, cancellationToken: cancellationToken); + await store1.PutAsync("b.2", 2, cancellationToken: cancellationToken); + await store1.PutAsync("c.1", 1, cancellationToken: cancellationToken); + await store1.PutAsync("c.2", 2, cancellationToken: cancellationToken); + await store1.PutAsync("d", 2, cancellationToken: cancellationToken); + + var ks1 = new List(); + + // Multiple keys are only supported in NATS Server 2.10 and later + await foreach (var k in store1.GetKeysAsync(new string[] { "d", "a.>", "c.>" }, cancellationToken: cancellationToken)) + { + ks1.Add(k); + } + + ks1.Sort(); + + Assert.Equal(new List { "a.1", "a.2", "c.1", "c.2", "d" }, ks1); + } }