diff --git a/src/StackExchange.Redis/Enums/Bitwise.cs b/src/StackExchange.Redis/Enums/Bitwise.cs index b38423eac..82e70b38a 100644 --- a/src/StackExchange.Redis/Enums/Bitwise.cs +++ b/src/StackExchange.Redis/Enums/Bitwise.cs @@ -24,5 +24,30 @@ public enum Bitwise /// Not /// Not, + + /// + /// DIFF operation: members of X that are not members of any of Y1, Y2, ... + /// Equivalent to X ∧ ¬(Y1 ∨ Y2 ∨ ...) + /// + Diff, + + /// + /// DIFF1 operation: members of one or more of Y1, Y2, ... that are not members of X + /// Equivalent to ¬X ∧ (Y1 ∨ Y2 ∨ ...) + /// + Diff1, + + /// + /// ANDOR operation: members of X that are also members of one or more of Y1, Y2, ... + /// Equivalent to X ∧ (Y1 ∨ Y2 ∨ ...) + /// + AndOr, + + /// + /// ONE operation: members of exactly one of X1, X2, ... + /// For two bitmaps this is equivalent to XOR. For more than two bitmaps, + /// this returns bits that are set in exactly one of the input bitmaps. + /// + One, } } diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 683332070..00ae49025 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -1899,3 +1899,7 @@ static StackExchange.Redis.RedisChannel.Sharded(byte[]? value) -> StackExchange. static StackExchange.Redis.RedisChannel.Sharded(string! value) -> StackExchange.Redis.RedisChannel StackExchange.Redis.ClientInfo.ShardedSubscriptionCount.get -> int StackExchange.Redis.ConfigurationOptions.SetUserPfxCertificate(string! userCertificatePath, string? password = null) -> void +StackExchange.Redis.Bitwise.AndOr = 6 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Diff = 4 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.Diff1 = 5 -> StackExchange.Redis.Bitwise +StackExchange.Redis.Bitwise.One = 7 -> StackExchange.Redis.Bitwise \ No newline at end of file diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs index faba07e68..06a44e643 100644 --- a/src/StackExchange.Redis/RedisFeatures.cs +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -44,7 +44,8 @@ namespace StackExchange.Redis v7_0_0_rc1 = new Version(6, 9, 240), // 7.0 RC1 is version 6.9.240 v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 - v7_4_0_rc2 = new Version(7, 3, 241); // 7.4 RC2 is version 7.3.241 + v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 + v8_2_0_rc1 = new Version(8, 1, 240); // 8.2 RC1 is version 8.1.240 #pragma warning restore SA1310 // Field names should not contain underscore #pragma warning restore SA1311 // Static readonly fields should begin with upper-case letter diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs index 549691fd2..29937c0dd 100644 --- a/src/StackExchange.Redis/RedisLiterals.cs +++ b/src/StackExchange.Redis/RedisLiterals.cs @@ -57,6 +57,7 @@ public static readonly RedisValue AGGREGATE = "AGGREGATE", ALPHA = "ALPHA", AND = "AND", + ANDOR = "ANDOR", ANY = "ANY", ASC = "ASC", AUTH = "AUTH", @@ -73,6 +74,8 @@ public static readonly RedisValue DB = "DB", @default = "default", DESC = "DESC", + DIFF = "DIFF", + DIFF1 = "DIFF1", DOCTOR = "DOCTOR", ENCODING = "ENCODING", EX = "EX", @@ -118,6 +121,7 @@ public static readonly RedisValue NUMSUB = "NUMSUB", NX = "NX", OBJECT = "OBJECT", + ONE = "ONE", OR = "OR", PATTERN = "PATTERN", PAUSE = "PAUSE", @@ -216,6 +220,10 @@ public static readonly RedisValue Bitwise.Or => OR, Bitwise.Xor => XOR, Bitwise.Not => NOT, + Bitwise.Diff => DIFF, + Bitwise.Diff1 => DIFF1, + Bitwise.AndOr => ANDOR, + Bitwise.One => ONE, _ => throw new ArgumentOutOfRangeException(nameof(operation)), }; } diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs index f467aca24..e632e44c9 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs @@ -1239,6 +1239,42 @@ public void StringBitOperation_2() mock.Received().StringBitOperation(Bitwise.Xor, "prefix:destination", Arg.Is(valid), CommandFlags.None); } + [Fact] + public void StringBitOperation_Diff() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + prefixed.StringBitOperation(Bitwise.Diff, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_Diff1() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + prefixed.StringBitOperation(Bitwise.Diff1, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.Diff1, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_AndOr() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + prefixed.StringBitOperation(Bitwise.AndOr, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.AndOr, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public void StringBitOperation_One() + { + RedisKey[] keys = new RedisKey[] { "a", "b", "c" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:a" && _[1] == "prefix:b" && _[2] == "prefix:c"; + prefixed.StringBitOperation(Bitwise.One, "destination", keys, CommandFlags.None); + mock.Received().StringBitOperation(Bitwise.One, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + [Fact] public void StringBitPosition() { diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs index aebc8fc6d..e639b04ce 100644 --- a/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs +++ b/tests/StackExchange.Redis.Tests/KeyPrefixedTests.cs @@ -1155,6 +1155,42 @@ public async Task StringBitOperationAsync_2() await mock.Received().StringBitOperationAsync(Bitwise.Xor, "prefix:destination", Arg.Is(valid), CommandFlags.None); } + [Fact] + public async Task StringBitOperationAsync_Diff() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + await prefixed.StringBitOperationAsync(Bitwise.Diff, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_Diff1() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + await prefixed.StringBitOperationAsync(Bitwise.Diff1, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.Diff1, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_AndOr() + { + RedisKey[] keys = new RedisKey[] { "x", "y1", "y2" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:x" && _[1] == "prefix:y1" && _[2] == "prefix:y2"; + await prefixed.StringBitOperationAsync(Bitwise.AndOr, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.AndOr, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + + [Fact] + public async Task StringBitOperationAsync_One() + { + RedisKey[] keys = new RedisKey[] { "a", "b", "c" }; + Expression> valid = _ => _.Length == 3 && _[0] == "prefix:a" && _[1] == "prefix:b" && _[2] == "prefix:c"; + await prefixed.StringBitOperationAsync(Bitwise.One, "destination", keys, CommandFlags.None); + await mock.Received().StringBitOperationAsync(Bitwise.One, "prefix:destination", Arg.Is(valid), CommandFlags.None); + } + [Fact] public async Task StringBitPositionAsync() { diff --git a/tests/StackExchange.Redis.Tests/StringTests.cs b/tests/StackExchange.Redis.Tests/StringTests.cs index 275f15fe2..d430b90a6 100644 --- a/tests/StackExchange.Redis.Tests/StringTests.cs +++ b/tests/StackExchange.Redis.Tests/StringTests.cs @@ -595,6 +595,279 @@ public async Task BitOp() Assert.Equal(unchecked((byte)(~3)), r_not); } + [Fact] + public async Task BitOpExtended() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyY3 = prefix + "Y3"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyX, keyY1, keyY2, keyY3 }, CommandFlags.FireAndForget); + + // Set up test data with more complex patterns + // X = 11110000 (240) + // Y1 = 10101010 (170) + // Y2 = 01010101 (85) + // Y3 = 11001100 (204) + db.StringSet(keyX, new byte[] { 240 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 170 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 85 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY3, new byte[] { 204 }, flags: CommandFlags.FireAndForget); + + // Test DIFF: X ∧ ¬(Y1 ∨ Y2 ∨ Y3) + // Y1 ∨ Y2 ∨ Y3 = 170 | 85 | 204 = 255 + // X ∧ ¬(Y1 ∨ Y2 ∨ Y3) = 240 & ~255 = 240 & 0 = 0 + var len_diff = await db.StringBitOperationAsync(Bitwise.Diff, "diff", new RedisKey[] { keyX, keyY1, keyY2, keyY3 }); + Assert.Equal(1, len_diff); + var r_diff = ((byte[]?)(await db.StringGetAsync("diff")))?.Single(); + Assert.Equal((byte)0, r_diff); + + // Test DIFF1: ¬X ∧ (Y1 ∨ Y2 ∨ Y3) + // ¬X = ~240 = 15 + // Y1 ∨ Y2 ∨ Y3 = 255 + // ¬X ∧ (Y1 ∨ Y2 ∨ Y3) = 15 & 255 = 15 + var len_diff1 = await db.StringBitOperationAsync(Bitwise.Diff1, "diff1", new RedisKey[] { keyX, keyY1, keyY2, keyY3 }); + Assert.Equal(1, len_diff1); + var r_diff1 = ((byte[]?)(await db.StringGetAsync("diff1")))?.Single(); + Assert.Equal((byte)15, r_diff1); + + // Test ANDOR: X ∧ (Y1 ∨ Y2 ∨ Y3) + // Y1 ∨ Y2 ∨ Y3 = 255 + // X ∧ (Y1 ∨ Y2 ∨ Y3) = 240 & 255 = 240 + var len_andor = await db.StringBitOperationAsync(Bitwise.AndOr, "andor", new RedisKey[] { keyX, keyY1, keyY2, keyY3 }); + Assert.Equal(1, len_andor); + var r_andor = ((byte[]?)(await db.StringGetAsync("andor")))?.Single(); + Assert.Equal((byte)240, r_andor); + + // Test ONE: bits set in exactly one bitmap + // For X=240, Y1=170, Y2=85, Y3=204 + // We need to count bits that appear in exactly one of these values + var len_one = await db.StringBitOperationAsync(Bitwise.One, "one", new RedisKey[] { keyX, keyY1, keyY2, keyY3 }); + Assert.Equal(1, len_one); + var r_one = ((byte[]?)(await db.StringGetAsync("one")))?.Single(); + + // Calculate expected ONE result manually + // Bit 7: X=1, Y1=1, Y2=0, Y3=1 -> count=3, not exactly 1 + // Bit 6: X=1, Y1=0, Y2=1, Y3=1 -> count=3, not exactly 1 + // Bit 5: X=1, Y1=1, Y2=0, Y3=0 -> count=2, not exactly 1 + // Bit 4: X=1, Y1=0, Y2=1, Y3=0 -> count=2, not exactly 1 + // Bit 3: X=0, Y1=1, Y2=0, Y3=1 -> count=2, not exactly 1 + // Bit 2: X=0, Y1=0, Y2=1, Y3=1 -> count=2, not exactly 1 + // Bit 1: X=0, Y1=1, Y2=0, Y3=0 -> count=1, exactly 1! -> bit should be set + // Bit 0: X=0, Y1=0, Y2=1, Y3=0 -> count=1, exactly 1! -> bit should be set + // Expected result: 00000011 = 3 + Assert.Equal((byte)3, r_one); + } + + [Fact] + public async Task BitOpTwoOperands() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var key1 = prefix + "1"; + var key2 = prefix + "2"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { key1, key2 }, CommandFlags.FireAndForget); + + // Test with two operands: key1=10101010 (170), key2=11001100 (204) + db.StringSet(key1, new byte[] { 170 }, flags: CommandFlags.FireAndForget); + db.StringSet(key2, new byte[] { 204 }, flags: CommandFlags.FireAndForget); + + // Test DIFF: key1 ∧ ¬key2 = 170 & ~204 = 170 & 51 = 34 + var len_diff = await db.StringBitOperationAsync(Bitwise.Diff, "diff2", new RedisKey[] { key1, key2 }); + Assert.Equal(1, len_diff); + var r_diff = ((byte[]?)(await db.StringGetAsync("diff2")))?.Single(); + Assert.Equal((byte)(170 & ~204), r_diff); + + // Test ONE with two operands (should be equivalent to XOR) + var len_one = await db.StringBitOperationAsync(Bitwise.One, "one2", new RedisKey[] { key1, key2 }); + Assert.Equal(1, len_one); + var r_one = ((byte[]?)(await db.StringGetAsync("one2")))?.Single(); + Assert.Equal((byte)(170 ^ 204), r_one); + + // Verify ONE equals XOR for two operands + var len_xor = await db.StringBitOperationAsync(Bitwise.Xor, "xor2", new RedisKey[] { key1, key2 }); + Assert.Equal(1, len_xor); + var r_xor = ((byte[]?)(await db.StringGetAsync("xor2")))?.Single(); + Assert.Equal(r_one, r_xor); + } + + [Fact] + public void BitOpDiff() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyX, keyY1, keyY2, keyResult }, CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000, Y2=01010000 + // Expected DIFF result: X ∧ ¬(Y1 ∨ Y2) = 11110000 ∧ ¬(11110000) = 00000000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.Diff, keyResult, new RedisKey[] { keyX, keyY1, keyY2 }); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // X ∧ ¬(Y1 ∨ Y2) = 11110000 ∧ ¬(11110000) = 11110000 ∧ 00001111 = 00000000 + Assert.Equal((byte)0b00000000, result); + } + + [Fact] + public void BitOpDiff1() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyX, keyY1, keyY2, keyResult }, CommandFlags.FireAndForget); + + // Set up test data: X=11000000, Y1=10100000, Y2=01010000 + // Expected DIFF1 result: ¬X ∧ (Y1 ∨ Y2) = ¬11000000 ∧ (10100000 ∨ 01010000) = 00111111 ∧ 11110000 = 00110000 + db.StringSet(keyX, new byte[] { 0b11000000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.Diff1, keyResult, new RedisKey[] { keyX, keyY1, keyY2 }); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // ¬X ∧ (Y1 ∨ Y2) = 00111111 ∧ 11110000 = 00110000 + Assert.Equal((byte)0b00110000, result); + } + + [Fact] + public void BitOpAndOr() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyY2 = prefix + "Y2"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyX, keyY1, keyY2, keyResult }, CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000, Y2=01010000 + // Expected ANDOR result: X ∧ (Y1 ∨ Y2) = 11110000 ∧ (10100000 ∨ 01010000) = 11110000 ∧ 11110000 = 11110000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.AndOr, keyResult, new RedisKey[] { keyX, keyY1, keyY2 }); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // X ∧ (Y1 ∨ Y2) = 11110000 ∧ 11110000 = 11110000 + Assert.Equal((byte)0b11110000, result); + } + + [Fact] + public void BitOpOne() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var key1 = prefix + "1"; + var key2 = prefix + "2"; + var key3 = prefix + "3"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { key1, key2, key3, keyResult }, CommandFlags.FireAndForget); + + // Set up test data: key1=10100000, key2=01010000, key3=00110000 + // Expected ONE result: bits set in exactly one bitmap = 11000000 + db.StringSet(key1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + db.StringSet(key2, new byte[] { 0b01010000 }, flags: CommandFlags.FireAndForget); + db.StringSet(key3, new byte[] { 0b00110000 }, flags: CommandFlags.FireAndForget); + + var length = db.StringBitOperation(Bitwise.One, keyResult, new RedisKey[] { key1, key2, key3 }); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + // Bits set in exactly one: position 7 (key1 only), position 6 (key2 only) = 11000000 + Assert.Equal((byte)0b11000000, result); + } + + [Fact] + public async Task BitOpDiffAsync() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyX = prefix + "X"; + var keyY1 = prefix + "Y1"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyX, keyY1, keyResult }, CommandFlags.FireAndForget); + + // Set up test data: X=11110000, Y1=10100000 + // Expected DIFF result: X ∧ ¬Y1 = 11110000 ∧ 01011111 = 01010000 + db.StringSet(keyX, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + db.StringSet(keyY1, new byte[] { 0b10100000 }, flags: CommandFlags.FireAndForget); + + var length = await db.StringBitOperationAsync(Bitwise.Diff, keyResult, new RedisKey[] { keyX, keyY1 }); + Assert.Equal(1, length); + + var result = ((byte[]?)await db.StringGetAsync(keyResult))?.Single(); + // X ∧ ¬Y1 = 11110000 ∧ 01011111 = 01010000 + Assert.Equal((byte)0b01010000, result); + } + + [Fact] + public void BitOpEdgeCases() + { + using var conn = Create(require: RedisFeatures.v8_2_0_rc1); + var db = conn.GetDatabase(); + var prefix = Me(); + var keyEmpty = prefix + "empty"; + var keyNonEmpty = prefix + "nonempty"; + var keyResult = prefix + "result"; + + // Clean up keys + db.KeyDelete(new RedisKey[] { keyEmpty, keyNonEmpty, keyResult }, CommandFlags.FireAndForget); + + // Test with empty bitmap + db.StringSet(keyNonEmpty, new byte[] { 0b11110000 }, flags: CommandFlags.FireAndForget); + + // DIFF with empty key should return the first key + var length = db.StringBitOperation(Bitwise.Diff, keyResult, new RedisKey[] { keyNonEmpty, keyEmpty }); + Assert.Equal(1, length); + + var result = ((byte[]?)db.StringGet(keyResult))?.Single(); + Assert.Equal((byte)0b11110000, result); + + // ONE with single key should return that key + length = db.StringBitOperation(Bitwise.One, keyResult, new RedisKey[] { keyNonEmpty }); + Assert.Equal(1, length); + + result = ((byte[]?)db.StringGet(keyResult))?.Single(); + Assert.Equal((byte)0b11110000, result); + } + [Fact] public async Task BitPosition() {