From e940b7c2f9fd75ad646d4126e538727a0ebef65e Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Sep 2026 14:37:07 +0100 Subject: [PATCH 1/3] CMS: add CELL_SIZE support, fix CMS.INFO field name, cover negative INCRBY - CMS.INITBYDIM/INITBYPROB take an optional cellSize (1, 2, 4, or 8 bytes) argument, sent as CELL_SIZE . - CmsInformation.CellSize now parses the "cell_size" field correctly; it previously looked for "cell size" (with a space) and always read -1. - Add regression tests for CELL_SIZE and for negative CMS.INCRBY increments (already passed through unchanged, now covered). --- .../CountMinSketch/CmsCommandBuilder.cs | 25 +++++- src/NRedisStack/CountMinSketch/CmsCommands.cs | 8 +- .../CountMinSketch/CmsCommandsAsync.cs | 8 +- .../CountMinSketch/ICmsCommands.cs | 6 +- .../CountMinSketch/ICmsCommandsAsync.cs | 6 +- .../CountMinSketch/Literals/CommandArgs.cs | 1 + .../PublicAPI/PublicAPI.Shipped.txt | 20 ++--- src/NRedisStack/ResponseParser.cs | 2 +- .../CountMinSketch/CmsTests.cs | 90 +++++++++++++++++++ 9 files changed, 139 insertions(+), 27 deletions(-) diff --git a/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs b/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs index 2f9dd6b2..a3612ca1 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs @@ -32,14 +32,31 @@ public static SerializedCommand Info(RedisKey key) return info; } - public static SerializedCommand InitByDim(RedisKey key, long width, long depth) + public static SerializedCommand InitByDim(RedisKey key, long width, long depth, int? cellSize = null) { - return new(CommandCategories.WriteAccumulating, CMS.INITBYDIM, key, width, depth); + List args = [key, width, depth]; + AddCellSize(args, cellSize); + + return new(CommandCategories.WriteAccumulating, CMS.INITBYDIM, args); } - public static SerializedCommand InitByProb(RedisKey key, double error, double probability) + public static SerializedCommand InitByProb(RedisKey key, double error, double probability, int? cellSize = null) { - return new(CommandCategories.WriteAccumulating, CMS.INITBYPROB, key, error, probability); + List args = [key, error, probability]; + AddCellSize(args, cellSize); + + return new(CommandCategories.WriteAccumulating, CMS.INITBYPROB, args); + } + + private static void AddCellSize(List args, int? cellSize) + { + if (cellSize is null) return; + + if (cellSize is not (1 or 2 or 4 or 8)) + throw new ArgumentOutOfRangeException(nameof(cellSize), cellSize, "Cell size must be 1, 2, 4, or 8."); + + args.Add(CmsArgs.CELL_SIZE); + args.Add(cellSize.Value); } public static SerializedCommand Merge(RedisValue destination, long numKeys, RedisValue[] source, diff --git a/src/NRedisStack/CountMinSketch/CmsCommands.cs b/src/NRedisStack/CountMinSketch/CmsCommands.cs index a193cd0b..ddbd21ea 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommands.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommands.cs @@ -32,15 +32,15 @@ public CmsInformation Info(RedisKey key) } /// - public bool InitByDim(RedisKey key, long width, long depth) + public bool InitByDim(RedisKey key, long width, long depth, int? cellSize = null) { - return _db.Execute(CmsCommandBuilder.InitByDim(key, width, depth)).OKtoBoolean(); + return _db.Execute(CmsCommandBuilder.InitByDim(key, width, depth, cellSize)).OKtoBoolean(); } /// - public bool InitByProb(RedisKey key, double error, double probability) + public bool InitByProb(RedisKey key, double error, double probability, int? cellSize = null) { - return _db.Execute(CmsCommandBuilder.InitByProb(key, error, probability)).OKtoBoolean(); + return _db.Execute(CmsCommandBuilder.InitByProb(key, error, probability, cellSize)).OKtoBoolean(); } /// diff --git a/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs b/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs index 7927a153..d0a4dc37 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs @@ -32,15 +32,15 @@ public async Task InfoAsync(RedisKey key) } /// - public async Task InitByDimAsync(RedisKey key, long width, long depth) + public async Task InitByDimAsync(RedisKey key, long width, long depth, int? cellSize = null) { - return (await _db.ExecuteAsync(CmsCommandBuilder.InitByDim(key, width, depth))).OKtoBoolean(); + return (await _db.ExecuteAsync(CmsCommandBuilder.InitByDim(key, width, depth, cellSize))).OKtoBoolean(); } /// - public async Task InitByProbAsync(RedisKey key, double error, double probability) + public async Task InitByProbAsync(RedisKey key, double error, double probability, int? cellSize = null) { - return (await _db.ExecuteAsync(CmsCommandBuilder.InitByProb(key, error, probability))).OKtoBoolean(); + return (await _db.ExecuteAsync(CmsCommandBuilder.InitByProb(key, error, probability, cellSize))).OKtoBoolean(); } /// diff --git a/src/NRedisStack/CountMinSketch/ICmsCommands.cs b/src/NRedisStack/CountMinSketch/ICmsCommands.cs index 178d9cfa..4408e585 100644 --- a/src/NRedisStack/CountMinSketch/ICmsCommands.cs +++ b/src/NRedisStack/CountMinSketch/ICmsCommands.cs @@ -40,9 +40,10 @@ public interface ICmsCommands /// Number of counters in each array. Reduces the error size. /// Number of counter-arrays. Reduces the probability for an error /// of a certain size (percentage of total count). + /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - bool InitByDim(RedisKey key, long width, long depth); + bool InitByDim(RedisKey key, long width, long depth, int? cellSize = null); /// /// Initializes a Count-Min Sketch to accommodate requested tolerances. @@ -50,9 +51,10 @@ public interface ICmsCommands /// The name of the sketch. /// Estimate size of error. /// The desired probability for inflated count. + /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - bool InitByProb(RedisKey key, double error, double probability); + bool InitByProb(RedisKey key, double error, double probability, int? cellSize = null); /// /// Merges several sketches into one sketch. diff --git a/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs b/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs index 1562d70f..80085314 100644 --- a/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs +++ b/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs @@ -40,9 +40,10 @@ public interface ICmsCommandsAsync /// Number of counters in each array. Reduces the error size. /// Number of counter-arrays. Reduces the probability for an error /// of a certain size (percentage of total count). + /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - Task InitByDimAsync(RedisKey key, long width, long depth); + Task InitByDimAsync(RedisKey key, long width, long depth, int? cellSize = null); /// /// Initializes a Count-Min Sketch to accommodate requested tolerances. @@ -50,9 +51,10 @@ public interface ICmsCommandsAsync /// The name of the sketch. /// Estimate size of error. /// The desired probability for inflated count. + /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - Task InitByProbAsync(RedisKey key, double error, double probability); + Task InitByProbAsync(RedisKey key, double error, double probability, int? cellSize = null); /// /// Merges several sketches into one sketch. diff --git a/src/NRedisStack/CountMinSketch/Literals/CommandArgs.cs b/src/NRedisStack/CountMinSketch/Literals/CommandArgs.cs index 9f9817eb..001d0c3f 100644 --- a/src/NRedisStack/CountMinSketch/Literals/CommandArgs.cs +++ b/src/NRedisStack/CountMinSketch/Literals/CommandArgs.cs @@ -3,4 +3,5 @@ namespace NRedisStack.CountMinSketch.Literals; internal class CmsArgs { public const string WEIGHTS = "WEIGHTS"; + public const string CELL_SIZE = "CELL_SIZE"; } \ No newline at end of file diff --git a/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt b/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt index 71f99ab6..efe1cba1 100644 --- a/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt +++ b/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt @@ -47,8 +47,8 @@ NRedisStack.CmsCommands.CmsCommands(StackExchange.Redis.IDatabase! db) -> void NRedisStack.CmsCommands.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> long NRedisStack.CmsCommands.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> long[]! NRedisStack.CmsCommands.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.CountMinSketch.DataTypes.CmsInformation! -NRedisStack.CmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> bool -NRedisStack.CmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> bool +NRedisStack.CmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> bool +NRedisStack.CmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> bool NRedisStack.CmsCommands.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> bool NRedisStack.CmsCommands.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> long[]! NRedisStack.CmsCommandsAsync @@ -56,8 +56,8 @@ NRedisStack.CmsCommandsAsync.CmsCommandsAsync(StackExchange.Redis.IDatabaseAsync NRedisStack.CmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.InfoAsync(StackExchange.Redis.RedisKey key) -> System.Threading.Tasks.Task! -NRedisStack.CmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth) -> System.Threading.Tasks.Task! -NRedisStack.CmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability) -> System.Threading.Tasks.Task! +NRedisStack.CmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> System.Threading.Tasks.Task! +NRedisStack.CmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.MergeAsync(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.QueryAsync(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> System.Threading.Tasks.Task! NRedisStack.Core.DataTypes.MinMaxModifier @@ -191,16 +191,16 @@ NRedisStack.ICmsCommands NRedisStack.ICmsCommands.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> long NRedisStack.ICmsCommands.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> long[]! NRedisStack.ICmsCommands.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.CountMinSketch.DataTypes.CmsInformation! -NRedisStack.ICmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> bool -NRedisStack.ICmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> bool +NRedisStack.ICmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> bool +NRedisStack.ICmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> bool NRedisStack.ICmsCommands.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> bool NRedisStack.ICmsCommands.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> long[]! NRedisStack.ICmsCommandsAsync NRedisStack.ICmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.InfoAsync(StackExchange.Redis.RedisKey key) -> System.Threading.Tasks.Task! -NRedisStack.ICmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth) -> System.Threading.Tasks.Task! -NRedisStack.ICmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.MergeAsync(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.QueryAsync(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> System.Threading.Tasks.Task! NRedisStack.ICuckooCommands @@ -1292,8 +1292,8 @@ static NRedisStack.BloomCommandBuilder.ScanDump(StackExchange.Redis.RedisKey key static NRedisStack.CmsCommandBuilder.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.RedisStackCommands.SerializedCommand! -static NRedisStack.CmsCommandBuilder.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> NRedisStack.RedisStackCommands.SerializedCommand! -static NRedisStack.CmsCommandBuilder.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> NRedisStack.RedisStackCommands.SerializedCommand! +static NRedisStack.CmsCommandBuilder.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> NRedisStack.RedisStackCommands.SerializedCommand! +static NRedisStack.CmsCommandBuilder.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.Core.DataTypes.MinMaxModifierExtensions.ToMinMax(this StackExchange.Redis.Order order) -> NRedisStack.Core.DataTypes.MinMaxModifier diff --git a/src/NRedisStack/ResponseParser.cs b/src/NRedisStack/ResponseParser.cs index 1fd1024a..b75115c9 100644 --- a/src/NRedisStack/ResponseParser.cs +++ b/src/NRedisStack/ResponseParser.cs @@ -516,7 +516,7 @@ public static CmsInformation case "count": count = (long)redisResults[i]; break; - case "cell size": + case "cell_size": cellSize = (int)redisResults[i]; break; } diff --git a/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs b/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs index f86491df..b3d47cd4 100644 --- a/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs +++ b/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs @@ -39,6 +39,48 @@ public async Task TestInitByDimAsync(string endpointId) Assert.Equal(0, info.Count); } + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public void TestInitByDimCellSize(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + cms.InitByDim(key, 16, 4, cellSize: 1); + var info = cms.Info(key); + + Assert.Equal(16, info.Width); + Assert.Equal(4, info.Depth); + Assert.Equal(0, info.Count); + Assert.Equal(1, info.CellSize); + } + + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public async Task TestInitByDimCellSizeAsync(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + await cms.InitByDimAsync(key, 16, 4, cellSize: 8); + var info = await cms.InfoAsync(key); + + Assert.Equal(16, info.Width); + Assert.Equal(4, info.Depth); + Assert.Equal(0, info.Count); + Assert.Equal(8, info.CellSize); + } + + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public void TestInitByDimInvalidCellSize(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + Assert.Throws(() => cms.InitByDim(key, 16, 4, cellSize: 3)); + } + [Theory] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public void TestInitByProb(string endpointId) @@ -54,6 +96,22 @@ public void TestInitByProb(string endpointId) Assert.Equal(0, info.Count); } + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public void TestInitByProbCellSize(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + cms.InitByProb(key, 0.01, 0.01, cellSize: 2); + var info = cms.Info(key); + + Assert.Equal(200, info.Width); + Assert.Equal(7, info.Depth); + Assert.Equal(0, info.Count); + Assert.Equal(2, info.CellSize); + } + [Theory] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public async Task TestInitByProbAsync(string endpointId) @@ -127,6 +185,38 @@ public async Task TestIncrByAsync(string endpointId) } + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public void TestIncrByNegative(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + cms.InitByDim(key, 1000, 5); + cms.IncrBy(key, "foo", 10); + var resp = cms.IncrBy(key, "foo", -4); + Assert.Equal(6, resp); + + var info = cms.Info(key); + Assert.Equal(6, info.Count); + } + + [Theory] + [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] + public async Task TestIncrByNegativeAsync(string endpointId) + { + var db = GetCleanDatabase(endpointId); + var cms = db.CMS(); + + await cms.InitByDimAsync(key, 1000, 5); + await cms.IncrByAsync(key, "foo", 10); + var resp = await cms.IncrByAsync(key, "foo", -4); + Assert.Equal(6, resp); + + var info = await cms.InfoAsync(key); + Assert.Equal(6, info.Count); + } + [Theory] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public void TestIncrByMultipleArgs(string endpointId) From 8ce6301acf9f5515b29c38be71e5f8faa3d0ad3c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Sep 2026 14:41:44 +0100 Subject: [PATCH 2/3] CMS: avoid binary break by adding CELL_SIZE as new overloads InitByDim/InitByProb (and their Async/interface/builder counterparts) keep their original 3-arg signatures; CELL_SIZE support is added via new 4-arg overloads instead of an extra optional parameter on the existing methods, so the existing compiled signatures are unchanged. --- .../CountMinSketch/CmsCommandBuilder.cs | 20 +++++++++---- src/NRedisStack/CountMinSketch/CmsCommands.cs | 16 ++++++++-- .../CountMinSketch/CmsCommandsAsync.cs | 16 ++++++++-- .../CountMinSketch/ICmsCommands.cs | 29 ++++++++++++++++--- .../CountMinSketch/ICmsCommandsAsync.cs | 29 ++++++++++++++++--- .../PublicAPI/PublicAPI.Shipped.txt | 20 ++++++------- .../PublicAPI/PublicAPI.Unshipped.txt | 10 +++++++ 7 files changed, 112 insertions(+), 28 deletions(-) diff --git a/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs b/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs index a3612ca1..1676018c 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommandBuilder.cs @@ -32,7 +32,12 @@ public static SerializedCommand Info(RedisKey key) return info; } - public static SerializedCommand InitByDim(RedisKey key, long width, long depth, int? cellSize = null) + public static SerializedCommand InitByDim(RedisKey key, long width, long depth) + { + return new(CommandCategories.WriteAccumulating, CMS.INITBYDIM, key, width, depth); + } + + public static SerializedCommand InitByDim(RedisKey key, long width, long depth, int cellSize) { List args = [key, width, depth]; AddCellSize(args, cellSize); @@ -40,7 +45,12 @@ public static SerializedCommand InitByDim(RedisKey key, long width, long depth, return new(CommandCategories.WriteAccumulating, CMS.INITBYDIM, args); } - public static SerializedCommand InitByProb(RedisKey key, double error, double probability, int? cellSize = null) + public static SerializedCommand InitByProb(RedisKey key, double error, double probability) + { + return new(CommandCategories.WriteAccumulating, CMS.INITBYPROB, key, error, probability); + } + + public static SerializedCommand InitByProb(RedisKey key, double error, double probability, int cellSize) { List args = [key, error, probability]; AddCellSize(args, cellSize); @@ -48,15 +58,13 @@ public static SerializedCommand InitByProb(RedisKey key, double error, double pr return new(CommandCategories.WriteAccumulating, CMS.INITBYPROB, args); } - private static void AddCellSize(List args, int? cellSize) + private static void AddCellSize(List args, int cellSize) { - if (cellSize is null) return; - if (cellSize is not (1 or 2 or 4 or 8)) throw new ArgumentOutOfRangeException(nameof(cellSize), cellSize, "Cell size must be 1, 2, 4, or 8."); args.Add(CmsArgs.CELL_SIZE); - args.Add(cellSize.Value); + args.Add(cellSize); } public static SerializedCommand Merge(RedisValue destination, long numKeys, RedisValue[] source, diff --git a/src/NRedisStack/CountMinSketch/CmsCommands.cs b/src/NRedisStack/CountMinSketch/CmsCommands.cs index ddbd21ea..0d0bf8e1 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommands.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommands.cs @@ -32,13 +32,25 @@ public CmsInformation Info(RedisKey key) } /// - public bool InitByDim(RedisKey key, long width, long depth, int? cellSize = null) + public bool InitByDim(RedisKey key, long width, long depth) + { + return _db.Execute(CmsCommandBuilder.InitByDim(key, width, depth)).OKtoBoolean(); + } + + /// + public bool InitByDim(RedisKey key, long width, long depth, int cellSize) { return _db.Execute(CmsCommandBuilder.InitByDim(key, width, depth, cellSize)).OKtoBoolean(); } /// - public bool InitByProb(RedisKey key, double error, double probability, int? cellSize = null) + public bool InitByProb(RedisKey key, double error, double probability) + { + return _db.Execute(CmsCommandBuilder.InitByProb(key, error, probability)).OKtoBoolean(); + } + + /// + public bool InitByProb(RedisKey key, double error, double probability, int cellSize) { return _db.Execute(CmsCommandBuilder.InitByProb(key, error, probability, cellSize)).OKtoBoolean(); } diff --git a/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs b/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs index d0a4dc37..1a7b6dbe 100644 --- a/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs +++ b/src/NRedisStack/CountMinSketch/CmsCommandsAsync.cs @@ -32,13 +32,25 @@ public async Task InfoAsync(RedisKey key) } /// - public async Task InitByDimAsync(RedisKey key, long width, long depth, int? cellSize = null) + public async Task InitByDimAsync(RedisKey key, long width, long depth) + { + return (await _db.ExecuteAsync(CmsCommandBuilder.InitByDim(key, width, depth))).OKtoBoolean(); + } + + /// + public async Task InitByDimAsync(RedisKey key, long width, long depth, int cellSize) { return (await _db.ExecuteAsync(CmsCommandBuilder.InitByDim(key, width, depth, cellSize))).OKtoBoolean(); } /// - public async Task InitByProbAsync(RedisKey key, double error, double probability, int? cellSize = null) + public async Task InitByProbAsync(RedisKey key, double error, double probability) + { + return (await _db.ExecuteAsync(CmsCommandBuilder.InitByProb(key, error, probability))).OKtoBoolean(); + } + + /// + public async Task InitByProbAsync(RedisKey key, double error, double probability, int cellSize) { return (await _db.ExecuteAsync(CmsCommandBuilder.InitByProb(key, error, probability, cellSize))).OKtoBoolean(); } diff --git a/src/NRedisStack/CountMinSketch/ICmsCommands.cs b/src/NRedisStack/CountMinSketch/ICmsCommands.cs index 4408e585..21b2c05d 100644 --- a/src/NRedisStack/CountMinSketch/ICmsCommands.cs +++ b/src/NRedisStack/CountMinSketch/ICmsCommands.cs @@ -40,10 +40,31 @@ public interface ICmsCommands /// Number of counters in each array. Reduces the error size. /// Number of counter-arrays. Reduces the probability for an error /// of a certain size (percentage of total count). - /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - bool InitByDim(RedisKey key, long width, long depth, int? cellSize = null); + bool InitByDim(RedisKey key, long width, long depth); + + /// + /// Initializes a Count-Min Sketch to dimensions specified by user. + /// + /// TThe name of the sketch. + /// Number of counters in each array. Reduces the error size. + /// Number of counter-arrays. Reduces the probability for an error + /// of a certain size (percentage of total count). + /// The number of bytes per cell (1, 2, 4, or 8). + /// if if executed correctly, Error otherwise. + /// + bool InitByDim(RedisKey key, long width, long depth, int cellSize); + + /// + /// Initializes a Count-Min Sketch to accommodate requested tolerances. + /// + /// The name of the sketch. + /// Estimate size of error. + /// The desired probability for inflated count. + /// if if executed correctly, Error otherwise. + /// + bool InitByProb(RedisKey key, double error, double probability); /// /// Initializes a Count-Min Sketch to accommodate requested tolerances. @@ -51,10 +72,10 @@ public interface ICmsCommands /// The name of the sketch. /// Estimate size of error. /// The desired probability for inflated count. - /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. + /// The number of bytes per cell (1, 2, 4, or 8). /// if if executed correctly, Error otherwise. /// - bool InitByProb(RedisKey key, double error, double probability, int? cellSize = null); + bool InitByProb(RedisKey key, double error, double probability, int cellSize); /// /// Merges several sketches into one sketch. diff --git a/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs b/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs index 80085314..8db46d54 100644 --- a/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs +++ b/src/NRedisStack/CountMinSketch/ICmsCommandsAsync.cs @@ -40,10 +40,31 @@ public interface ICmsCommandsAsync /// Number of counters in each array. Reduces the error size. /// Number of counter-arrays. Reduces the probability for an error /// of a certain size (percentage of total count). - /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. /// if if executed correctly, Error otherwise. /// - Task InitByDimAsync(RedisKey key, long width, long depth, int? cellSize = null); + Task InitByDimAsync(RedisKey key, long width, long depth); + + /// + /// Initializes a Count-Min Sketch to dimensions specified by user. + /// + /// TThe name of the sketch. + /// Number of counters in each array. Reduces the error size. + /// Number of counter-arrays. Reduces the probability for an error + /// of a certain size (percentage of total count). + /// The number of bytes per cell (1, 2, 4, or 8). + /// if if executed correctly, Error otherwise. + /// + Task InitByDimAsync(RedisKey key, long width, long depth, int cellSize); + + /// + /// Initializes a Count-Min Sketch to accommodate requested tolerances. + /// + /// The name of the sketch. + /// Estimate size of error. + /// The desired probability for inflated count. + /// if if executed correctly, Error otherwise. + /// + Task InitByProbAsync(RedisKey key, double error, double probability); /// /// Initializes a Count-Min Sketch to accommodate requested tolerances. @@ -51,10 +72,10 @@ public interface ICmsCommandsAsync /// The name of the sketch. /// Estimate size of error. /// The desired probability for inflated count. - /// The number of bytes per cell (1, 2, 4, or 8). Defaults to 4 on the server. + /// The number of bytes per cell (1, 2, 4, or 8). /// if if executed correctly, Error otherwise. /// - Task InitByProbAsync(RedisKey key, double error, double probability, int? cellSize = null); + Task InitByProbAsync(RedisKey key, double error, double probability, int cellSize); /// /// Merges several sketches into one sketch. diff --git a/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt b/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt index efe1cba1..71f99ab6 100644 --- a/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt +++ b/src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt @@ -47,8 +47,8 @@ NRedisStack.CmsCommands.CmsCommands(StackExchange.Redis.IDatabase! db) -> void NRedisStack.CmsCommands.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> long NRedisStack.CmsCommands.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> long[]! NRedisStack.CmsCommands.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.CountMinSketch.DataTypes.CmsInformation! -NRedisStack.CmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> bool -NRedisStack.CmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> bool +NRedisStack.CmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> bool +NRedisStack.CmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> bool NRedisStack.CmsCommands.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> bool NRedisStack.CmsCommands.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> long[]! NRedisStack.CmsCommandsAsync @@ -56,8 +56,8 @@ NRedisStack.CmsCommandsAsync.CmsCommandsAsync(StackExchange.Redis.IDatabaseAsync NRedisStack.CmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.InfoAsync(StackExchange.Redis.RedisKey key) -> System.Threading.Tasks.Task! -NRedisStack.CmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> System.Threading.Tasks.Task! -NRedisStack.CmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> System.Threading.Tasks.Task! +NRedisStack.CmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth) -> System.Threading.Tasks.Task! +NRedisStack.CmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.MergeAsync(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> System.Threading.Tasks.Task! NRedisStack.CmsCommandsAsync.QueryAsync(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> System.Threading.Tasks.Task! NRedisStack.Core.DataTypes.MinMaxModifier @@ -191,16 +191,16 @@ NRedisStack.ICmsCommands NRedisStack.ICmsCommands.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> long NRedisStack.ICmsCommands.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> long[]! NRedisStack.ICmsCommands.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.CountMinSketch.DataTypes.CmsInformation! -NRedisStack.ICmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> bool -NRedisStack.ICmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> bool +NRedisStack.ICmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> bool +NRedisStack.ICmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> bool NRedisStack.ICmsCommands.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> bool NRedisStack.ICmsCommands.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> long[]! NRedisStack.ICmsCommandsAsync NRedisStack.ICmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.IncrByAsync(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.InfoAsync(StackExchange.Redis.RedisKey key) -> System.Threading.Tasks.Task! -NRedisStack.ICmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> System.Threading.Tasks.Task! -NRedisStack.ICmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.MergeAsync(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> System.Threading.Tasks.Task! NRedisStack.ICmsCommandsAsync.QueryAsync(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> System.Threading.Tasks.Task! NRedisStack.ICuckooCommands @@ -1292,8 +1292,8 @@ static NRedisStack.BloomCommandBuilder.ScanDump(StackExchange.Redis.RedisKey key static NRedisStack.CmsCommandBuilder.IncrBy(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue item, long increment) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.IncrBy(StackExchange.Redis.RedisKey key, System.Tuple![]! itemIncrements) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Info(StackExchange.Redis.RedisKey key) -> NRedisStack.RedisStackCommands.SerializedCommand! -static NRedisStack.CmsCommandBuilder.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int? cellSize = null) -> NRedisStack.RedisStackCommands.SerializedCommand! -static NRedisStack.CmsCommandBuilder.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int? cellSize = null) -> NRedisStack.RedisStackCommands.SerializedCommand! +static NRedisStack.CmsCommandBuilder.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth) -> NRedisStack.RedisStackCommands.SerializedCommand! +static NRedisStack.CmsCommandBuilder.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Merge(StackExchange.Redis.RedisValue destination, long numKeys, StackExchange.Redis.RedisValue[]! source, long[]? weight = null) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.CmsCommandBuilder.Query(StackExchange.Redis.RedisKey key, params StackExchange.Redis.RedisValue[]! items) -> NRedisStack.RedisStackCommands.SerializedCommand! static NRedisStack.Core.DataTypes.MinMaxModifierExtensions.ToMinMax(this StackExchange.Redis.Order order) -> NRedisStack.Core.DataTypes.MinMaxModifier diff --git a/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt b/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt index 666bcf99..9a7151b7 100644 --- a/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt @@ -1,2 +1,12 @@ #nullable enable NRedisStack.CountMinSketch.DataTypes.CmsInformation.CellSize.get -> int +NRedisStack.CmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int cellSize) -> bool +NRedisStack.CmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int cellSize) -> bool +NRedisStack.CmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int cellSize) -> System.Threading.Tasks.Task! +NRedisStack.CmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int cellSize) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommands.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int cellSize) -> bool +NRedisStack.ICmsCommands.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int cellSize) -> bool +NRedisStack.ICmsCommandsAsync.InitByDimAsync(StackExchange.Redis.RedisKey key, long width, long depth, int cellSize) -> System.Threading.Tasks.Task! +NRedisStack.ICmsCommandsAsync.InitByProbAsync(StackExchange.Redis.RedisKey key, double error, double probability, int cellSize) -> System.Threading.Tasks.Task! +static NRedisStack.CmsCommandBuilder.InitByDim(StackExchange.Redis.RedisKey key, long width, long depth, int cellSize) -> NRedisStack.RedisStackCommands.SerializedCommand! +static NRedisStack.CmsCommandBuilder.InitByProb(StackExchange.Redis.RedisKey key, double error, double probability, int cellSize) -> NRedisStack.RedisStackCommands.SerializedCommand! From 8f6540774e3ea63068296ab4b41971dcbcdcd85e Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Sep 2026 15:53:37 +0100 Subject: [PATCH 3/3] CMS: gate CELL_SIZE/negative-INCRBY tests to 8.12+, add 8.12 CI matrix row - CMS.INITBYDIM/INITBYPROB CELL_SIZE tests and CMS.INCRBY negative-increment tests are SkipIfRedisTheory-gated at 8.12.0, since older servers reject the CELL_SIZE argument (wrong number of arguments) and negative increments. - Add tests/dockers/.env.v8.12, pinned to an unstable preview image pending an official 8.12 release, and add "8.12" to both the PR/push and nightly redis-version matrices in integration.yml. --- .github/workflows/integration.yml | 6 +++--- tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs | 10 +++++----- tests/dockers/.env.v8.12 | 7 +++++++ 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 tests/dockers/.env.v8.12 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7572f458..914ea9c4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -39,10 +39,10 @@ jobs: fail-fast: false matrix: # Full version set runs only on the nightly schedule; PRs and pushes - # test a reduced set (7.2 LTS, 7.4 LTS, 8.2 LTS, 8.8, 8.10 current stable) to keep CI fast. + # test a reduced set (7.2 LTS, 7.4 LTS, 8.2 LTS, 8.8, 8.10 current stable, 8.12 preview) to keep CI fast. redis-version: ${{ fromJSON(github.event_name == 'schedule' - && '["8.10", "8.8", "8.6", "8.4", "8.2", "7.4", "7.2", "6.2"]' - || '["8.10", "8.8", "8.2", "7.4", "7.2"]') }} + && '["8.12", "8.10", "8.8", "8.6", "8.4", "8.2", "7.4", "7.2", "6.2"]' + || '["8.12", "8.10", "8.8", "8.2", "7.4", "7.2"]') }} dotnet-version: ['8.0', '9.0', '10.0'] name: Redis ${{ matrix.redis-version }}; .NET ${{ matrix.dotnet-version }}; steps: diff --git a/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs b/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs index b3d47cd4..5d213508 100644 --- a/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs +++ b/tests/NRedisStack.Tests/CountMinSketch/CmsTests.cs @@ -39,7 +39,7 @@ public async Task TestInitByDimAsync(string endpointId) Assert.Equal(0, info.Count); } - [Theory] + [SkipIfRedisTheory(Comparison.LessThan, "8.12.0")] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public void TestInitByDimCellSize(string endpointId) { @@ -55,7 +55,7 @@ public void TestInitByDimCellSize(string endpointId) Assert.Equal(1, info.CellSize); } - [Theory] + [SkipIfRedisTheory(Comparison.LessThan, "8.12.0")] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public async Task TestInitByDimCellSizeAsync(string endpointId) { @@ -96,7 +96,7 @@ public void TestInitByProb(string endpointId) Assert.Equal(0, info.Count); } - [Theory] + [SkipIfRedisTheory(Comparison.LessThan, "8.12.0")] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public void TestInitByProbCellSize(string endpointId) { @@ -185,7 +185,7 @@ public async Task TestIncrByAsync(string endpointId) } - [Theory] + [SkipIfRedisTheory(Comparison.LessThan, "8.12.0")] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public void TestIncrByNegative(string endpointId) { @@ -201,7 +201,7 @@ public void TestIncrByNegative(string endpointId) Assert.Equal(6, info.Count); } - [Theory] + [SkipIfRedisTheory(Comparison.LessThan, "8.12.0")] [MemberData(nameof(EndpointsFixture.Env.AllEnvironments), MemberType = typeof(EndpointsFixture.Env))] public async Task TestIncrByNegativeAsync(string endpointId) { diff --git a/tests/dockers/.env.v8.12 b/tests/dockers/.env.v8.12 new file mode 100644 index 00000000..55ecf15f --- /dev/null +++ b/tests/dockers/.env.v8.12 @@ -0,0 +1,7 @@ +# Environment variables for Redis 8.12 +# Used by the run-tests GitHub Action + +# No official 8.12 release image exists yet, so this is pinned to an unstable +# preview build. Unlike the other .env.v* files, this tag is not guaranteed to +# stay available - replace with the real release image once 8.12 ships. +CLIENT_LIBS_TEST_IMAGE=redislabs/client-libs-test:unstable-34786335206-debian