-
Couldn't load subscription status.
- Fork 2
Implement Cluster Scan #113
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
cf4bbf1 to
ca014f5
Compare
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
…n `Request.GenericCommands`. Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
…y bank") Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
… methods Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
Signed-off-by: currantw <[email protected]>
…r_scan_cursor` in `lib.rs`. Signed-off-by: currantw <[email protected]>
…scan_cursor`. Signed-off-by: currantw <[email protected]>
…tion Signed-off-by: currantw <[email protected]>
ca014f5 to
05d8fb9
Compare
Signed-off-by: currantw <[email protected]>
| .vs | ||
| .vscode | ||
| .kiro/ | ||
| .amazonq/ |
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.
Unrelated addition. Supports Amazon Q memory bank.
| /// * `args` and `arg_lengths` must be valid arrays of length `arg_count` | ||
| /// * `args` format: [b"MATCH", pattern, b"COUNT", count, b"TYPE", type] (all optional) | ||
| #[unsafe(no_mangle)] | ||
| pub unsafe extern "C-unwind" fn request_cluster_scan( |
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.
Don't have any experience with Rust. Implementation modelled on existing cluster scan methods here. Any feedback appreciated!
| let scan_state_cursor = if cursor_id == "0" { | ||
| redis::ScanStateRC::new() | ||
| } else { | ||
| match glide_core::cluster_scan_container::get_cluster_scan_cursor(cursor_id.clone()) { | ||
| Ok(existing_cursor) => existing_cursor, | ||
| Err(_error) => { | ||
| unsafe { | ||
| (core.failure_callback)( | ||
| callback_index, | ||
| format!("Invalid cursor ID: {}", cursor_id).as_ptr() as *const c_char, | ||
| RequestErrorType::Unspecified, | ||
| ); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| }; |
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.
Updated to handle the case (raise an error) where an invalid cursor is provided, as described in this pull request.
| #region protected fields | ||
| protected Version? _serverVersion; // cached server version | ||
| protected static readonly Version DefaultServerVersion = new(8, 0, 0); | ||
| #endregion protected fields |
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.
Not new. Just moved from "private fields" region below (since these obviously are not private...)
| => Simple<bool>(RequestType.Move, [key.ToGlideString(), database.ToGlideString()]); | ||
|
|
||
| public static Cmd<object[], (long, ValkeyKey[])> ScanAsync(long cursor, ValkeyValue pattern = default, long pageSize = 0) | ||
| public static Cmd<object[], (string, ValkeyKey[])> ScanAsync(string cursor, ScanOptions? options = null) |
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.
Updated to use ScanOptions and for consistency with the cluster interface.
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.
We can only change this API because the earlier C# release was a public preview right?
|
|
||
| protected abstract Task<Version> GetServerVersionAsync(); | ||
|
|
||
| protected async Task<(string cursor, ValkeyKey[] keys)> ClusterScanCommand(string cursor, string[] args) |
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 would have liked to put this in GlideClusterClient, since it is actually only applicable to the cluster client (obviously). But that would require changing the access modifier for _messageContainer, which I'm not sure is advisable? @jbrinkman / @alexr-bq any thoughts?
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.
Would it be possible to give _messageContainer an internal access level? That way arbitrary user code that extends BaseClient doesn't have access to this method.
Signed-off-by: currantw <[email protected]>
| () => Assert.Equal(["SCAN", "0", "TYPE", "string"], Request.ScanAsync("0", new ScanOptions { Type = ValkeyType.String }).GetArgs()), | ||
| () => Assert.Equal(["SCAN", "5", "TYPE", "list"], Request.ScanAsync("5", new ScanOptions { Type = ValkeyType.List }).GetArgs()), | ||
| () => Assert.Equal(["SCAN", "0", "TYPE", "set"], Request.ScanAsync("0", new ScanOptions { Type = ValkeyType.Set }).GetArgs()), | ||
| () => Assert.Equal(["SCAN", "0", "TYPE", "zset"], Request.ScanAsync("0", new ScanOptions { Type = ValkeyType.SortedSet }).GetArgs()), | ||
| () => Assert.Equal(["SCAN", "0", "TYPE", "hash"], Request.ScanAsync("0", new ScanOptions { Type = ValkeyType.Hash }).GetArgs()), | ||
| () => Assert.Equal(["SCAN", "0", "TYPE", "stream"], Request.ScanAsync("0", new ScanOptions { Type = ValkeyType.Stream }).GetArgs()), |
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.
Added additional tests here for TYPE, which wasn't supported before.
| /// </code> | ||
| /// </example> | ||
| /// <seealso href="https://valkey.io/commands/scan/">SCAN command</seealso> | ||
| Task<(string cursor, ValkeyKey[] keys)> ScanAsync(string cursor, ScanOptions? options = null); |
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.
Standalone ScanAsync added for consistency with other clients. There is still the existing KeysAsync, which matches the existing StackExchange.Redis interface, but that does not provide support for the filtering keys by type.
Signed-off-by: currantw <[email protected]>
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.
Pull Request Overview
This PR implements cluster and standalone scan functionality for the Valkey GLIDE client. The changes introduce a consistent ScanAsync API across both client types while adding support for filtering by pattern, type, and count options.
Key Changes:
- Added
ScanAsyncmethods toIGenericCommands(standalone) andIGenericClusterCommands(cluster) interfaces with implementations in respective clients - Introduced
ScanOptionsclass supporting match pattern, count, and type filtering, andClusterScanCursorfor cluster scan state management - Refactored internal
Request.ScanAsyncto useScanOptionsinstead of individual parameters, updating cursor handling fromlongtostring
Reviewed Changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Valkey.Glide.UnitTests/CommandTests.cs | Updated unit tests to use new ScanAsync signature with ScanOptions and string cursors |
| tests/Valkey.Glide.IntegrationTests/ScanTests.cs | Added comprehensive integration tests for scan functionality with filtering options |
| sources/Valkey.Glide/abstract_APITypes/ClusterScanCursor.cs | New cursor abstraction for cluster scan operations |
| sources/Valkey.Glide/Internals/Request.GenericCommands.cs | Refactored scan command builder to accept ScanOptions and return string cursors |
| sources/Valkey.Glide/Internals/FFI.methods.cs | Added FFI bindings for cluster scan operations |
| sources/Valkey.Glide/GlideClusterClient.cs | Implemented ScanAsync for cluster client using new cluster scan command |
| sources/Valkey.Glide/GlideClient.cs | Implemented ScanAsync for standalone client and updated KeysAsync to use new scan interface |
| sources/Valkey.Glide/Commands/Options/ScanOptions.cs | New options class for scan configuration |
| sources/Valkey.Glide/Commands/IGenericCommands.cs | Added ScanAsync method signature for standalone client |
| sources/Valkey.Glide/Commands/IGenericClusterCommands.cs | Added ScanAsync method signature for cluster client |
| sources/Valkey.Glide/Commands/Constants/Constants.cs | Added TYPE keyword constant |
| sources/Valkey.Glide/BaseClient.cs | Added ClusterScanCommand implementation and relocated server version fields |
| rust/src/lib.rs | Implemented Rust FFI functions for cluster scan operations |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Signed-off-by: currantw <[email protected]>
| do | ||
| { | ||
| (long nextCursor, ValkeyKey[] keys) = await Command(Request.ScanAsync(currentCursor, pattern, pageSize)); | ||
| var options = new ScanOptions(); |
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: let's allocate this outside the loop and reuse
Implement Cluster Scan
Overview
Adds
ScanAsyncmethods to the standalone and cluster clients.Key Changes
ScanAsynctoIGenericClusterCommandsand implemented it inGlideClusterClient, which calls newBaseClient.ClusterScanCommandmethod.ScanAsynctoIGenericCommands(standalone client) and implemented it inGlideClusterClient, which calls the existing Glide SCAN command. Provides consistent scan interface for standalone and cluster clients.ClusterScanCursor, while both standalone and cluster scanning useScanOptions, which supports match pattern, count, and type arguments. Refactor existingRequest.GenericCommands.ScanAsyncandKeysAsyncmethod to useScanOptions.RequestClusterScanFfiandRemoveClusterScanCursorFfimethods to "FFI.methods.rs", as well as Addrequest_cluster_scanandremove_cluster_scan_cursorto "rust/src/lib.rs" that call into the existing core methods (implementation based on equivalent methods here).Testing Coverage
ScanTestsfor both standalone and clusterScanAsyncmethods.CommandTestsfor change to internalRequest.ScanAsyncinterface.Breaking Changes
None.