-
Notifications
You must be signed in to change notification settings - Fork 2
Bitmap command support #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
Signed-off-by: Alex Rehnby-Martin <[email protected]>
| name: net${{ matrix.dotnet }}, server ${{ matrix.server.version }}, ${{ matrix.host.TARGET }} | ||
| needs: get-matrices | ||
| timeout-minutes: 100 | ||
| timeout-minutes: 200 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still necessary? If so, is there an issue to investigate and try to sped these up?
| | Engine Type | 6.2 | 7.0 | 7.1 | 7.2 | 8.0 | 8.1 | 9.0 | | ||
| |-----------------------|-------|-------|--------|-------|-------|-------|-------| | ||
| | Valkey | - | - | - | V | V | V | V | | ||
| | Redis | V | V | V | V | - | - | - | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making this update!
| namespace Valkey.Glide.Commands; | ||
|
|
||
| /// <summary> | ||
| /// Supports commands for the "Bitmap Commands" group for standalone and cluster clients. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit. Maybe a bit simpler?
| /// Supports commands for the "Bitmap Commands" group for standalone and cluster clients. | |
| /// Supports bitmap commands for standalone and cluster clients. |
| /// <summary> | ||
| /// Sets or clears the bit at offset in the string value stored at key. | ||
| /// </summary> | ||
| /// <seealso href="https://valkey.io/commands/setbit"/> | ||
| /// <param name="key">The key of the string.</param> | ||
| /// <param name="offset">The offset in the string to set the bit at.</param> | ||
| /// <param name="value">The bit value to set (true for 1, false for 0).</param> | ||
| /// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param> | ||
| /// <returns>The original bit value stored at offset.</returns> | ||
| /// <remarks> | ||
| /// <example> | ||
| /// <code> | ||
| /// bool oldBit = await client.StringSetBitAsync("mykey", 1, true); | ||
| /// Console.WriteLine(oldBit); // Output: false (original bit value) | ||
| /// </code> | ||
| /// </example> | ||
| /// </remarks> | ||
| Task<bool> StringSetBitAsync(ValkeyKey key, long offset, bool value, CommandFlags flags = CommandFlags.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth specifying the behaviour when the key doesn't exist or when the offset is bigger than the string? Or are you happy just deferring to the linked documentation? I think either approach work, but you seem to have different levels of detail/defferal in StringGetBitAsync compared to StringSetBitAsync?
|
|
||
| public static Cmd<long, long> BitPositionAsync(ValkeyKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte) | ||
| { | ||
| List<GlideString> args = [key.ToGlideString(), (bit ? 1 : 0).ToGlideString(), start.ToGlideString(), end.ToGlideString()]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've got this repeated in a few places. Is it worth adding a ToGlideString extension for bool so that we can directly do these conversions? I imagine that there are a few other places that could use that?
| List<GlideString> args = [key.ToGlideString(), (bit ? 1 : 0).ToGlideString(), start.ToGlideString(), end.ToGlideString()]; | |
| List<GlideString> args = [key.ToGlideString(), bit.ToGlideString(), start.ToGlideString(), end.ToGlideString()]; |
| /// <inheritdoc/> | ||
| public async Task<bool> StringGetBitAsync(ValkeyKey key, long offset, CommandFlags flags = CommandFlags.None) | ||
| { | ||
| Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is duplicated so many times. Can we extract it to a BaseClient method like ThrowIfCommandFlags()? Also happy to raise a separate issue/separate PR in the interest of getting this merged 🤷 (If you can raise it, I'm happy to complete it!)
| public static Cmd<long, bool> SetBitAsync(ValkeyKey key, long offset, bool value) | ||
| => new(RequestType.SetBit, [key.ToGlideString(), offset.ToGlideString(), (value ? 1 : 0).ToGlideString()], false, response => response != 0); | ||
|
|
||
| public static Cmd<long, long> BitCountAsync(ValkeyKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this (and other methods here) need default values? It seems like the methods that call into here already have defaults for all these values, so (a) will these values ever not be populated when this (and similar) methods are called, and (b) are we duplicating these defaults?
| // Test bit positions in 'A' (01000001) | ||
| bool bit0 = await client.StringGetBitAsync(key, 0); // Should be false (0) | ||
| bool bit1 = await client.StringGetBitAsync(key, 1); // Should be true (1) | ||
| bool bit2 = await client.StringGetBitAsync(key, 2); // Should be false (0) | ||
| bool bit6 = await client.StringGetBitAsync(key, 6); // Should be false (0) | ||
| bool bit7 = await client.StringGetBitAsync(key, 7); // Should be true (1) | ||
|
|
||
| Assert.False(bit0); | ||
| Assert.True(bit1); | ||
| Assert.False(bit2); | ||
| Assert.False(bit6); | ||
| Assert.True(bit7); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that SETBIT supports negative offsets. Does GETBIT do the same?
Also nit. Doesn't seem necessary to have these comments or store results instead of testing them directly? 🤷
Similar to other tests below. Just seems like more code to read, maintain, etc.?
| // Test bit positions in 'A' (01000001) | |
| bool bit0 = await client.StringGetBitAsync(key, 0); // Should be false (0) | |
| bool bit1 = await client.StringGetBitAsync(key, 1); // Should be true (1) | |
| bool bit2 = await client.StringGetBitAsync(key, 2); // Should be false (0) | |
| bool bit6 = await client.StringGetBitAsync(key, 6); // Should be false (0) | |
| bool bit7 = await client.StringGetBitAsync(key, 7); // Should be true (1) | |
| Assert.False(bit0); | |
| Assert.True(bit1); | |
| Assert.False(bit2); | |
| Assert.False(bit6); | |
| Assert.True(bit7); | |
| // Test bit positions in 'A' (01000001) | |
| Assert.False(await client.StringGetBitAsync(key, 0)); | |
| Assert.True(await client.StringGetBitAsync(key, 1)); | |
| Assert.False(await client.StringGetBitAsync(key, 2)); | |
| Assert.False(await client.StringGetBitAsync(key, 6)); | |
| Assert.True(await client.StringGetBitAsync(key, 7)); |
| [Theory(DisableDiscoveryEnumeration = true)] | ||
| [MemberData(nameof(Config.TestClients), MemberType = typeof(TestConfiguration))] | ||
| public async Task BitField_AutoOptimization_UsesReadOnlyForGetOperations(BaseClient client) | ||
| { | ||
| string key = Guid.NewGuid().ToString(); | ||
|
|
||
| // Set initial value "A" (ASCII 65 = 01000001) | ||
| await client.StringSetAsync(key, "A"); | ||
|
|
||
| // Test with only GET operations - should automatically use BITFIELD_RO | ||
| var readOnlySubCommands = new BitFieldOptions.IBitFieldSubCommand[] | ||
| { | ||
| new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(8), new BitFieldOptions.BitOffset(0)), | ||
| new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(4), new BitFieldOptions.BitOffset(0)), | ||
| new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(4), new BitFieldOptions.BitOffset(4)) | ||
| }; | ||
|
|
||
| // This should internally use BITFIELD_RO since all commands are GET | ||
| long[] results = await client.StringBitFieldAsync(key, readOnlySubCommands); | ||
|
|
||
| Assert.Equal(3, results.Length); | ||
| Assert.Equal(65, results[0]); // Full 8 bits: 01000001 = 65 | ||
| Assert.Equal(4, results[1]); // First 4 bits: 0100 = 4 | ||
| Assert.Equal(1, results[2]); // Next 4 bits: 0001 = 1 | ||
|
|
||
| // Test with mixed operations - should use regular BITFIELD | ||
| var mixedSubCommands = new BitFieldOptions.IBitFieldSubCommand[] | ||
| { | ||
| new BitFieldOptions.BitFieldGet(BitFieldOptions.Encoding.Unsigned(8), new BitFieldOptions.BitOffset(0)), | ||
| new BitFieldOptions.BitFieldSet(BitFieldOptions.Encoding.Unsigned(8), new BitFieldOptions.BitOffset(0), 100) | ||
| }; | ||
|
|
||
| long[] mixedResults = await client.StringBitFieldAsync(key, mixedSubCommands); | ||
|
|
||
| Assert.Equal(2, mixedResults.Length); | ||
| Assert.Equal(65, mixedResults[0]); // Original value 'A' | ||
| Assert.Equal(65, mixedResults[1]); // Old value before SET | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this test actually verifying that BITFIELD_RO is called internally?
… 7.0.0+ Signed-off-by: Alex Rehnby-Martin <[email protected]>
Adds support for bitmap commands set out in #52