diff --git a/.github/workflows/ort.yml b/.github/workflows/ort.yml index 70ff0bbc22..b6c0508eb8 100644 --- a/.github/workflows/ort.yml +++ b/.github/workflows/ort.yml @@ -52,6 +52,11 @@ jobs: with: submodules: "true" ref: ${{ env.BASE_BRANCH }} + # This is a temporary fix, till ORT will fix thire issue with newer v of Cargo - https://github.com/oss-review-toolkit/ort/issues/8480 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.76 + with: + targets: ${{ inputs.target }} - name: Set up JDK 11 for the ORT package uses: actions/setup-java@v4 @@ -204,4 +209,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} EVENT_NAME: ${{ github.event_name }} INPUT_VERSION: ${{ github.event.inputs.version }} - \ No newline at end of file + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7384172e..917ce07d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ #### Changes * Python: Added JSON.DEL JSON.FORGET commands ([#1146](https://github.com/aws/glide-for-redis/pull/1146)) +* Python: Added STRLEN command ([#1230](https://github.com/aws/glide-for-redis/pull/1230)) +* Python: Added HKEYS command ([#1228](https://github.com/aws/glide-for-redis/pull/1228)) +* Python: Added ZREMRANGEBYSCORE command ([#1151](https://github.com/aws/glide-for-redis/pull/1151)) +* Node: Added SPOP, SPOPCOUNT commands. ([#1117](https://github.com/aws/glide-for-redis/pull/1117)) * Node: Added `BLPOP` command ([#1223](https://github.com/aws/glide-for-redis/pull/1223)) #### Fixes diff --git a/csharp/lib/AsyncClient.cs b/csharp/lib/AsyncClient.cs index 83e3d4c39b..38ee7984f9 100644 --- a/csharp/lib/AsyncClient.cs +++ b/csharp/lib/AsyncClient.cs @@ -2,6 +2,7 @@ * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +using System.Buffers; using System.Runtime.InteropServices; namespace Glide; @@ -22,18 +23,35 @@ public AsyncClient(string host, UInt32 port, bool useTLS) } } - public async Task SetAsync(string key, string value) + private async Task command(IntPtr[] args, int argsCount, RequestType requestType) { - var message = messageContainer.GetMessageForCall(key, value); - SetFfi(clientPointer, (ulong)message.Index, message.KeyPtr, message.ValuePtr); - await message; + // We need to pin the array in place, in order to ensure that the GC doesn't move it while the operation is running. + GCHandle pinnedArray = GCHandle.Alloc(args, GCHandleType.Pinned); + IntPtr pointer = pinnedArray.AddrOfPinnedObject(); + var message = messageContainer.GetMessageForCall(args, argsCount); + CommandFfi(clientPointer, (ulong)message.Index, (int)requestType, pointer, (uint)argsCount); + var result = await message; + pinnedArray.Free(); + return result; + } + + public async Task SetAsync(string key, string value) + { + var args = this.arrayPool.Rent(2); + args[0] = Marshal.StringToHGlobalAnsi(key); + args[1] = Marshal.StringToHGlobalAnsi(value); + var result = await command(args, 2, RequestType.SetString); + this.arrayPool.Return(args); + return result; } public async Task GetAsync(string key) { - var message = messageContainer.GetMessageForCall(key, null); - GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr); - return await message; + var args = this.arrayPool.Rent(1); + args[0] = Marshal.StringToHGlobalAnsi(key); + var result = await command(args, 1, RequestType.GetString); + this.arrayPool.Return(args); + return result; } public void Dispose() @@ -89,6 +107,7 @@ private void FailureCallback(ulong index) private IntPtr clientPointer; private readonly MessageContainer messageContainer = new(); + private readonly ArrayPool arrayPool = ArrayPool.Shared; #endregion private fields @@ -96,11 +115,8 @@ private void FailureCallback(ulong index) private delegate void StringAction(ulong index, IntPtr str); private delegate void FailureAction(ulong index); - [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")] - private static extern void GetFfi(IntPtr client, ulong index, IntPtr key); - - [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "set")] - private static extern void SetFfi(IntPtr client, ulong index, IntPtr key, IntPtr value); + [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "command")] + private static extern void CommandFfi(IntPtr client, ulong index, Int32 requestType, IntPtr args, UInt32 argCount); private delegate void IntAction(IntPtr arg); [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_client")] @@ -110,4 +126,114 @@ private void FailureCallback(ulong index) private static extern void CloseClientFfi(IntPtr client); #endregion + + #region RequestType + + // TODO: generate this with a bindings generator + private enum RequestType + { + InvalidRequest = 0, + CustomCommand = 1, + GetString = 2, + SetString = 3, + Ping = 4, + Info = 5, + Del = 6, + Select = 7, + ConfigGet = 8, + ConfigSet = 9, + ConfigResetStat = 10, + ConfigRewrite = 11, + ClientGetName = 12, + ClientGetRedir = 13, + ClientId = 14, + ClientInfo = 15, + ClientKill = 16, + ClientList = 17, + ClientNoEvict = 18, + ClientNoTouch = 19, + ClientPause = 20, + ClientReply = 21, + ClientSetInfo = 22, + ClientSetName = 23, + ClientUnblock = 24, + ClientUnpause = 25, + Expire = 26, + HashSet = 27, + HashGet = 28, + HashDel = 29, + HashExists = 30, + MGet = 31, + MSet = 32, + Incr = 33, + IncrBy = 34, + Decr = 35, + IncrByFloat = 36, + DecrBy = 37, + HashGetAll = 38, + HashMSet = 39, + HashMGet = 40, + HashIncrBy = 41, + HashIncrByFloat = 42, + LPush = 43, + LPop = 44, + RPush = 45, + RPop = 46, + LLen = 47, + LRem = 48, + LRange = 49, + LTrim = 50, + SAdd = 51, + SRem = 52, + SMembers = 53, + SCard = 54, + PExpireAt = 55, + PExpire = 56, + ExpireAt = 57, + Exists = 58, + Unlink = 59, + TTL = 60, + Zadd = 61, + Zrem = 62, + Zrange = 63, + Zcard = 64, + Zcount = 65, + ZIncrBy = 66, + ZScore = 67, + Type = 68, + HLen = 69, + Echo = 70, + ZPopMin = 71, + Strlen = 72, + Lindex = 73, + ZPopMax = 74, + XRead = 75, + XAdd = 76, + XReadGroup = 77, + XAck = 78, + XTrim = 79, + XGroupCreate = 80, + XGroupDestroy = 81, + HSetNX = 82, + SIsMember = 83, + Hvals = 84, + PTTL = 85, + ZRemRangeByRank = 86, + Persist = 87, + ZRemRangeByScore = 88, + Time = 89, + Zrank = 90, + Rename = 91, + DBSize = 92, + Brpop = 93, + Hkeys = 94, + PfAdd = 96, + PfCount = 97, + PfMerge = 98, + Blpop = 100, + RPushX = 102, + LPushX = 103, + } + + #endregion } diff --git a/csharp/lib/Message.cs b/csharp/lib/Message.cs index c0d4c7f07b..5ecb994d0b 100644 --- a/csharp/lib/Message.cs +++ b/csharp/lib/Message.cs @@ -17,11 +17,10 @@ internal class Message : INotifyCompletion /// know how to find the message and set its result. public int Index { get; } - /// The pointer to the unmanaged memory that contains the operation's key. - public IntPtr KeyPtr { get; private set; } - - /// The pointer to the unmanaged memory that contains the operation's key. - public IntPtr ValuePtr { get; private set; } + /// The array holding the pointers to the unmanaged memory that contains the operation's arguments. + public IntPtr[]? args { get; private set; } + // We need to save the args count, because sometimes we get arrays that are larger than they need to be. We can't rely on `this.args.Length`, due to it coming from an array pool. + private int argsCount; private readonly MessageContainer container; public Message(int index, MessageContainer container) @@ -84,30 +83,29 @@ private void CheckRaceAndCallContinuation() /// This returns a task that will complete once SetException / SetResult are called, /// and ensures that the internal state of the message is set-up before the task is created, /// and cleaned once it is complete. - public void StartTask(string? key, string? value, object client) + public void SetupTask(IntPtr[] args, int argsCount, object client) { continuation = null; this.completionState = COMPLETION_STAGE_STARTED; this.result = default(T); this.exception = null; this.client = client; - this.KeyPtr = key is null ? IntPtr.Zero : Marshal.StringToHGlobalAnsi(key); - this.ValuePtr = value is null ? IntPtr.Zero : Marshal.StringToHGlobalAnsi(value); + this.args = args; + this.argsCount = argsCount; } // This function isn't thread-safe. Access to it should be from a single thread, and only once per operation. // For the sake of performance, this responsibility is on the caller, and the function doesn't contain any safety measures. private void FreePointers() { - if (KeyPtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(KeyPtr); - KeyPtr = IntPtr.Zero; - } - if (ValuePtr != IntPtr.Zero) + if (this.args is not null) { - Marshal.FreeHGlobal(ValuePtr); - ValuePtr = IntPtr.Zero; + for (var i = 0; i < this.argsCount; i++) + { + Marshal.FreeHGlobal(this.args[i]); + } + this.args = null; + this.argsCount = 0; } client = null; } diff --git a/csharp/lib/MessageContainer.cs b/csharp/lib/MessageContainer.cs index faa1b5a277..14d4267723 100644 --- a/csharp/lib/MessageContainer.cs +++ b/csharp/lib/MessageContainer.cs @@ -11,10 +11,10 @@ internal class MessageContainer { internal Message GetMessage(int index) => messages[index]; - internal Message GetMessageForCall(string? key, string? value) + internal Message GetMessageForCall(IntPtr[] args, int argsCount) { var message = GetFreeMessage(); - message.StartTask(key, value, this); + message.SetupTask(args, argsCount, this); return message; } diff --git a/csharp/lib/src/lib.rs b/csharp/lib/src/lib.rs index 8baa6d0155..fce015a376 100644 --- a/csharp/lib/src/lib.rs +++ b/csharp/lib/src/lib.rs @@ -3,7 +3,8 @@ */ use glide_core::client; use glide_core::client::Client as GlideClient; -use redis::{Cmd, FromRedisValue, RedisResult}; +use glide_core::request_type::RequestType; +use redis::{FromRedisValue, RedisResult}; use std::{ ffi::{c_void, CStr, CString}, os::raw::c_char, @@ -91,61 +92,43 @@ pub extern "C" fn close_client(client_ptr: *const c_void) { /// Expects that key and value will be kept valid until the callback is called. #[no_mangle] -pub extern "C" fn set( +pub extern "C" fn command( client_ptr: *const c_void, callback_index: usize, - key: *const c_char, - value: *const c_char, + request_type: RequestType, + args: *const *mut c_char, + arg_count: u32, ) { let client = unsafe { Box::leak(Box::from_raw(client_ptr as *mut Client)) }; - // The safety of this needs to be ensured by the calling code. Cannot dispose of the pointer before all operations have completed. - let ptr_address = client_ptr as usize; - - let key_cstring = unsafe { CStr::from_ptr(key as *mut c_char) }; - let value_cstring = unsafe { CStr::from_ptr(value as *mut c_char) }; - let mut client_clone = client.client.clone(); - client.runtime.spawn(async move { - let key_bytes = key_cstring.to_bytes(); - let value_bytes = value_cstring.to_bytes(); - let mut cmd = Cmd::new(); - cmd.arg("SET").arg(key_bytes).arg(value_bytes); - let result = client_clone.send_command(&cmd, None).await; - unsafe { - let client = Box::leak(Box::from_raw(ptr_address as *mut Client)); - match result { - Ok(_) => (client.success_callback)(callback_index, std::ptr::null()), // TODO - should return "OK" string. - Err(_) => (client.failure_callback)(callback_index), // TODO - report errors - }; - } - }); -} -/// Expects that key will be kept valid until the callback is called. If the callback is called with a string pointer, the pointer must -/// be used synchronously, because the string will be dropped after the callback. -#[no_mangle] -pub extern "C" fn get(client_ptr: *const c_void, callback_index: usize, key: *const c_char) { - let client = unsafe { Box::leak(Box::from_raw(client_ptr as *mut Client)) }; - // The safety of this needs to be ensured by the calling code. Cannot dispose of the pointer before all operations have completed. + // The safety of these needs to be ensured by the calling code. Cannot dispose of the pointer before all operations have completed. let ptr_address = client_ptr as usize; + let args_address = args as usize; - let key_cstring = unsafe { CStr::from_ptr(key as *mut c_char) }; let mut client_clone = client.client.clone(); client.runtime.spawn(async move { - let key_bytes = key_cstring.to_bytes(); - let mut cmd = Cmd::new(); - cmd.arg("GET").arg(key_bytes); - let result = client_clone.send_command(&cmd, None).await; - let client = unsafe { Box::leak(Box::from_raw(ptr_address as *mut Client)) }; - let value = match result { - Ok(value) => value, - Err(_) => { - unsafe { (client.failure_callback)(callback_index) }; // TODO - report errors, + let Some(mut cmd) = request_type.get_command() else { + unsafe { + let client = Box::leak(Box::from_raw(ptr_address as *mut Client)); + (client.failure_callback)(callback_index); // TODO - report errors return; } }; - let result = Option::::from_owned_redis_value(value); + let args_slice = unsafe { + std::slice::from_raw_parts(args_address as *const *mut c_char, arg_count as usize) + }; + for arg in args_slice { + let c_str = unsafe { CStr::from_ptr(*arg as *mut c_char) }; + cmd.arg(c_str.to_bytes()); + } + + let result = client_clone + .send_command(&cmd, None) + .await + .and_then(Option::::from_owned_redis_value); unsafe { + let client = Box::leak(Box::from_raw(ptr_address as *mut Client)); match result { Ok(None) => (client.success_callback)(callback_index, std::ptr::null()), Ok(Some(c_str)) => (client.success_callback)(callback_index, c_str.as_ptr()), diff --git a/csharp/tests/Integration/GetAndSet.cs b/csharp/tests/Integration/GetAndSet.cs index 98ac38beaa..17362e3cc1 100644 --- a/csharp/tests/Integration/GetAndSet.cs +++ b/csharp/tests/Integration/GetAndSet.cs @@ -12,13 +12,19 @@ namespace tests.Integration; public class GetAndSet { + private async Task GetAndSetValues(AsyncClient client, string key, string value) + { + var setResult = await client.SetAsync(key, value); + Assert.That(setResult, Is.EqualTo("OK")); + var result = await client.GetAsync(key); + Assert.That(result, Is.EqualTo(value)); + } + private async Task GetAndSetRandomValues(AsyncClient client) { var key = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString(); - await client.SetAsync(key, value); - var result = await client.GetAsync(key); - Assert.That(result, Is.EqualTo(value)); + await GetAndSetValues(client, key, value); } [Test] @@ -37,9 +43,7 @@ public async Task GetAndSetCanHandleNonASCIIUnicode() { var key = Guid.NewGuid().ToString(); var value = "שלום hello 汉字"; - await client.SetAsync(key, value); - var result = await client.GetAsync(key); - Assert.That(result, Is.EqualTo(value)); + await GetAndSetValues(client, key, value); } } @@ -60,9 +64,7 @@ public async Task GetReturnsEmptyString() { var key = Guid.NewGuid().ToString(); var value = ""; - await client.SetAsync(key, value); - var result = await client.GetAsync(key); - Assert.That(result, Is.EqualTo(value)); + await GetAndSetValues(client, key, value); } } @@ -81,9 +83,7 @@ public async Task HandleVeryLargeInput() { value += value; } - await client.SetAsync(key, value); - var result = await client.GetAsync(key); - Assert.That(result, Is.EqualTo(value)); + await GetAndSetValues(client, key, value); } } diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 85c087e254..0733f7230f 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -2764,7 +2764,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.21.7 +Package: base64:0.22.0 The following copyrights and licenses were found in the source code of this package: @@ -3934,7 +3934,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.35 +Package: chrono:0.4.37 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9734,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.12 +Package: getrandom:0.2.13 The following copyrights and licenses were found in the source code of this package: @@ -12538,7 +12538,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libredox:0.0.1 +Package: libredox:0.1.3 The following copyrights and licenses were found in the source code of this package: @@ -13229,7 +13229,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: memchr:2.7.1 +Package: memchr:2.7.2 The following copyrights and licenses were found in the source code of this package: @@ -16582,7 +16582,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.13 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -19207,7 +19207,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redox_users:0.4.4 +Package: redox_users:0.4.5 The following copyrights and licenses were found in the source code of this package: @@ -19947,7 +19947,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.1 +Package: rustls-pemfile:2.1.2 The following copyrights and licenses were found in the source code of this package: @@ -20190,7 +20190,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.4.0 +Package: rustls-pki-types:1.4.1 The following copyrights and licenses were found in the source code of this package: @@ -20925,7 +20925,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.9.2 +Package: security-framework:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -21154,7 +21154,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.9.1 +Package: security-framework-sys:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -21897,693 +21897,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: signal-hook:0.3.17 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-registry:1.4.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-tokio:0.3.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: @@ -23352,7 +22665,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.55 +Package: syn:2.0.58 The following copyrights and licenses were found in the source code of this package: @@ -25453,7 +24766,7 @@ the following restrictions: ---- -Package: tokio:1.36.0 +Package: tokio:1.37.0 The following copyrights and licenses were found in the source code of this package: diff --git a/glide-core/src/lib.rs b/glide-core/src/lib.rs index bd194f008f..f904928be1 100644 --- a/glide-core/src/lib.rs +++ b/glide-core/src/lib.rs @@ -15,3 +15,4 @@ pub use socket_listener::*; pub mod errors; pub mod scripts_container; pub use client::ConnectionRequest; +pub mod request_type; diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 3bf9f48ae1..08428b92ea 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -135,7 +135,14 @@ enum RequestType { Rename = 91; DBSize = 92; Brpop = 93; - Blpop = 94; + Hkeys = 94; + Spop = 95; + PfAdd = 96; + PfCount = 97; + PfMerge = 98; + Blpop = 100; + RPushX = 102; + LPushX = 103; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs new file mode 100644 index 0000000000..ffed834871 --- /dev/null +++ b/glide-core/src/request_type.rs @@ -0,0 +1,340 @@ +/** + * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + */ +use redis::{cmd, Cmd}; + +#[cfg(feature = "socket-layer")] +use crate::redis_request::RequestType as ProtobufRequestType; + +#[repr(C)] +#[derive(Debug)] +pub enum RequestType { + InvalidRequest = 0, + CustomCommand = 1, + GetString = 2, + SetString = 3, + Ping = 4, + Info = 5, + Del = 6, + Select = 7, + ConfigGet = 8, + ConfigSet = 9, + ConfigResetStat = 10, + ConfigRewrite = 11, + ClientGetName = 12, + ClientGetRedir = 13, + ClientId = 14, + ClientInfo = 15, + ClientKill = 16, + ClientList = 17, + ClientNoEvict = 18, + ClientNoTouch = 19, + ClientPause = 20, + ClientReply = 21, + ClientSetInfo = 22, + ClientSetName = 23, + ClientUnblock = 24, + ClientUnpause = 25, + Expire = 26, + HashSet = 27, + HashGet = 28, + HashDel = 29, + HashExists = 30, + MGet = 31, + MSet = 32, + Incr = 33, + IncrBy = 34, + Decr = 35, + IncrByFloat = 36, + DecrBy = 37, + HashGetAll = 38, + HashMSet = 39, + HashMGet = 40, + HashIncrBy = 41, + HashIncrByFloat = 42, + LPush = 43, + LPop = 44, + RPush = 45, + RPop = 46, + LLen = 47, + LRem = 48, + LRange = 49, + LTrim = 50, + SAdd = 51, + SRem = 52, + SMembers = 53, + SCard = 54, + PExpireAt = 55, + PExpire = 56, + ExpireAt = 57, + Exists = 58, + Unlink = 59, + TTL = 60, + Zadd = 61, + Zrem = 62, + Zrange = 63, + Zcard = 64, + Zcount = 65, + ZIncrBy = 66, + ZScore = 67, + Type = 68, + HLen = 69, + Echo = 70, + ZPopMin = 71, + Strlen = 72, + Lindex = 73, + ZPopMax = 74, + XRead = 75, + XAdd = 76, + XReadGroup = 77, + XAck = 78, + XTrim = 79, + XGroupCreate = 80, + XGroupDestroy = 81, + HSetNX = 82, + SIsMember = 83, + Hvals = 84, + PTTL = 85, + ZRemRangeByRank = 86, + Persist = 87, + ZRemRangeByScore = 88, + Time = 89, + Zrank = 90, + Rename = 91, + DBSize = 92, + Brpop = 93, + Hkeys = 94, + Spop = 95, + PfAdd = 96, + PfCount = 97, + PfMerge = 98, + Blpop = 100, + RPushX = 102, + LPushX = 103, +} + +fn get_two_word_command(first: &str, second: &str) -> Cmd { + let mut cmd = cmd(first); + cmd.arg(second); + cmd +} + +#[cfg(feature = "socket-layer")] +impl From<::protobuf::EnumOrUnknown> for RequestType { + fn from(value: ::protobuf::EnumOrUnknown) -> Self { + match value.enum_value_or(ProtobufRequestType::InvalidRequest) { + ProtobufRequestType::InvalidRequest => RequestType::InvalidRequest, + ProtobufRequestType::CustomCommand => RequestType::CustomCommand, + ProtobufRequestType::GetString => RequestType::GetString, + ProtobufRequestType::SetString => RequestType::SetString, + ProtobufRequestType::Ping => RequestType::Ping, + ProtobufRequestType::Info => RequestType::Info, + ProtobufRequestType::Del => RequestType::Del, + ProtobufRequestType::Select => RequestType::Select, + ProtobufRequestType::ConfigGet => RequestType::ConfigGet, + ProtobufRequestType::ConfigSet => RequestType::ConfigSet, + ProtobufRequestType::ConfigResetStat => RequestType::ConfigResetStat, + ProtobufRequestType::ConfigRewrite => RequestType::ConfigRewrite, + ProtobufRequestType::ClientGetName => RequestType::ClientGetName, + ProtobufRequestType::ClientGetRedir => RequestType::ClientGetRedir, + ProtobufRequestType::ClientId => RequestType::ClientId, + ProtobufRequestType::ClientInfo => RequestType::ClientInfo, + ProtobufRequestType::ClientKill => RequestType::ClientKill, + ProtobufRequestType::ClientList => RequestType::ClientList, + ProtobufRequestType::ClientNoEvict => RequestType::ClientNoEvict, + ProtobufRequestType::ClientNoTouch => RequestType::ClientNoTouch, + ProtobufRequestType::ClientPause => RequestType::ClientPause, + ProtobufRequestType::ClientReply => RequestType::ClientReply, + ProtobufRequestType::ClientSetInfo => RequestType::ClientSetInfo, + ProtobufRequestType::ClientSetName => RequestType::ClientSetName, + ProtobufRequestType::ClientUnblock => RequestType::ClientUnblock, + ProtobufRequestType::ClientUnpause => RequestType::ClientUnpause, + ProtobufRequestType::Expire => RequestType::Expire, + ProtobufRequestType::HashSet => RequestType::HashSet, + ProtobufRequestType::HashGet => RequestType::HashGet, + ProtobufRequestType::HashDel => RequestType::HashDel, + ProtobufRequestType::HashExists => RequestType::HashExists, + ProtobufRequestType::MSet => RequestType::MSet, + ProtobufRequestType::MGet => RequestType::MGet, + ProtobufRequestType::Incr => RequestType::Incr, + ProtobufRequestType::IncrBy => RequestType::IncrBy, + ProtobufRequestType::IncrByFloat => RequestType::IncrByFloat, + ProtobufRequestType::Decr => RequestType::Decr, + ProtobufRequestType::DecrBy => RequestType::DecrBy, + ProtobufRequestType::HashGetAll => RequestType::HashGetAll, + ProtobufRequestType::HashMSet => RequestType::HashMSet, + ProtobufRequestType::HashMGet => RequestType::HashMGet, + ProtobufRequestType::HashIncrBy => RequestType::HashIncrBy, + ProtobufRequestType::HashIncrByFloat => RequestType::HashIncrByFloat, + ProtobufRequestType::LPush => RequestType::LPush, + ProtobufRequestType::LPop => RequestType::LPop, + ProtobufRequestType::RPush => RequestType::RPush, + ProtobufRequestType::RPop => RequestType::RPop, + ProtobufRequestType::LLen => RequestType::LLen, + ProtobufRequestType::LRem => RequestType::LRem, + ProtobufRequestType::LRange => RequestType::LRange, + ProtobufRequestType::LTrim => RequestType::LTrim, + ProtobufRequestType::SAdd => RequestType::SAdd, + ProtobufRequestType::SRem => RequestType::SRem, + ProtobufRequestType::SMembers => RequestType::SMembers, + ProtobufRequestType::SCard => RequestType::SCard, + ProtobufRequestType::PExpireAt => RequestType::PExpireAt, + ProtobufRequestType::PExpire => RequestType::PExpire, + ProtobufRequestType::ExpireAt => RequestType::ExpireAt, + ProtobufRequestType::Exists => RequestType::Exists, + ProtobufRequestType::Unlink => RequestType::Unlink, + ProtobufRequestType::TTL => RequestType::TTL, + ProtobufRequestType::Zadd => RequestType::Zadd, + ProtobufRequestType::Zrem => RequestType::Zrem, + ProtobufRequestType::Zrange => RequestType::Zrange, + ProtobufRequestType::Zcard => RequestType::Zcard, + ProtobufRequestType::Zcount => RequestType::Zcount, + ProtobufRequestType::ZIncrBy => RequestType::ZIncrBy, + ProtobufRequestType::ZScore => RequestType::ZScore, + ProtobufRequestType::Type => RequestType::Type, + ProtobufRequestType::HLen => RequestType::HLen, + ProtobufRequestType::Echo => RequestType::Echo, + ProtobufRequestType::ZPopMin => RequestType::ZPopMin, + ProtobufRequestType::Strlen => RequestType::Strlen, + ProtobufRequestType::Lindex => RequestType::Lindex, + ProtobufRequestType::ZPopMax => RequestType::ZPopMax, + ProtobufRequestType::XAck => RequestType::XAck, + ProtobufRequestType::XAdd => RequestType::XAdd, + ProtobufRequestType::XReadGroup => RequestType::XReadGroup, + ProtobufRequestType::XRead => RequestType::XRead, + ProtobufRequestType::XGroupCreate => RequestType::XGroupCreate, + ProtobufRequestType::XGroupDestroy => RequestType::XGroupDestroy, + ProtobufRequestType::XTrim => RequestType::XTrim, + ProtobufRequestType::HSetNX => RequestType::HSetNX, + ProtobufRequestType::SIsMember => RequestType::SIsMember, + ProtobufRequestType::Hvals => RequestType::Hvals, + ProtobufRequestType::PTTL => RequestType::PTTL, + ProtobufRequestType::ZRemRangeByRank => RequestType::ZRemRangeByRank, + ProtobufRequestType::Persist => RequestType::Persist, + ProtobufRequestType::ZRemRangeByScore => RequestType::ZRemRangeByScore, + ProtobufRequestType::Time => RequestType::Time, + ProtobufRequestType::Zrank => RequestType::Zrank, + ProtobufRequestType::Rename => RequestType::Rename, + ProtobufRequestType::DBSize => RequestType::DBSize, + ProtobufRequestType::Brpop => RequestType::Brpop, + ProtobufRequestType::Hkeys => RequestType::Hkeys, + ProtobufRequestType::PfAdd => RequestType::PfAdd, + ProtobufRequestType::PfCount => RequestType::PfCount, + ProtobufRequestType::PfMerge => RequestType::PfMerge, + ProtobufRequestType::RPushX => RequestType::RPushX, + ProtobufRequestType::LPushX => RequestType::LPushX, + ProtobufRequestType::Blpop => RequestType::Blpop, + ProtobufRequestType::Spop => RequestType::Spop, + } + } +} + +impl RequestType { + /// Returns a `Cmd` set with the command name matching the request. + pub fn get_command(&self) -> Option { + match self { + RequestType::InvalidRequest => None, + RequestType::CustomCommand => Some(Cmd::new()), + RequestType::GetString => Some(cmd("GET")), + RequestType::SetString => Some(cmd("SET")), + RequestType::Ping => Some(cmd("PING")), + RequestType::Info => Some(cmd("INFO")), + RequestType::Del => Some(cmd("DEL")), + RequestType::Select => Some(cmd("SELECT")), + RequestType::ConfigGet => Some(get_two_word_command("CONFIG", "GET")), + RequestType::ConfigSet => Some(get_two_word_command("CONFIG", "SET")), + RequestType::ConfigResetStat => Some(get_two_word_command("CONFIG", "RESETSTAT")), + RequestType::ConfigRewrite => Some(get_two_word_command("CONFIG", "REWRITE")), + RequestType::ClientGetName => Some(get_two_word_command("CLIENT", "GETNAME")), + RequestType::ClientGetRedir => Some(get_two_word_command("CLIENT", "GETREDIR")), + RequestType::ClientId => Some(get_two_word_command("CLIENT", "ID")), + RequestType::ClientInfo => Some(get_two_word_command("CLIENT", "INFO")), + RequestType::ClientKill => Some(get_two_word_command("CLIENT", "KILL")), + RequestType::ClientList => Some(get_two_word_command("CLIENT", "LIST")), + RequestType::ClientNoEvict => Some(get_two_word_command("CLIENT", "NO-EVICT")), + RequestType::ClientNoTouch => Some(get_two_word_command("CLIENT", "NO-TOUCH")), + RequestType::ClientPause => Some(get_two_word_command("CLIENT", "PAUSE")), + RequestType::ClientReply => Some(get_two_word_command("CLIENT", "REPLY")), + RequestType::ClientSetInfo => Some(get_two_word_command("CLIENT", "SETINFO")), + RequestType::ClientSetName => Some(get_two_word_command("CLIENT", "SETNAME")), + RequestType::ClientUnblock => Some(get_two_word_command("CLIENT", "UNBLOCK")), + RequestType::ClientUnpause => Some(get_two_word_command("CLIENT", "UNPAUSE")), + RequestType::Expire => Some(cmd("EXPIRE")), + RequestType::HashSet => Some(cmd("HSET")), + RequestType::HashGet => Some(cmd("HGET")), + RequestType::HashDel => Some(cmd("HDEL")), + RequestType::HashExists => Some(cmd("HEXISTS")), + RequestType::MSet => Some(cmd("MSET")), + RequestType::MGet => Some(cmd("MGET")), + RequestType::Incr => Some(cmd("INCR")), + RequestType::IncrBy => Some(cmd("INCRBY")), + RequestType::IncrByFloat => Some(cmd("INCRBYFLOAT")), + RequestType::Decr => Some(cmd("DECR")), + RequestType::DecrBy => Some(cmd("DECRBY")), + RequestType::HashGetAll => Some(cmd("HGETALL")), + RequestType::HashMSet => Some(cmd("HMSET")), + RequestType::HashMGet => Some(cmd("HMGET")), + RequestType::HashIncrBy => Some(cmd("HINCRBY")), + RequestType::HashIncrByFloat => Some(cmd("HINCRBYFLOAT")), + RequestType::LPush => Some(cmd("LPUSH")), + RequestType::LPop => Some(cmd("LPOP")), + RequestType::RPush => Some(cmd("RPUSH")), + RequestType::RPop => Some(cmd("RPOP")), + RequestType::LLen => Some(cmd("LLEN")), + RequestType::LRem => Some(cmd("LREM")), + RequestType::LRange => Some(cmd("LRANGE")), + RequestType::LTrim => Some(cmd("LTRIM")), + RequestType::SAdd => Some(cmd("SADD")), + RequestType::SRem => Some(cmd("SREM")), + RequestType::SMembers => Some(cmd("SMEMBERS")), + RequestType::SCard => Some(cmd("SCARD")), + RequestType::PExpireAt => Some(cmd("PEXPIREAT")), + RequestType::PExpire => Some(cmd("PEXPIRE")), + RequestType::ExpireAt => Some(cmd("EXPIREAT")), + RequestType::Exists => Some(cmd("EXISTS")), + RequestType::Unlink => Some(cmd("UNLINK")), + RequestType::TTL => Some(cmd("TTL")), + RequestType::Zadd => Some(cmd("ZADD")), + RequestType::Zrem => Some(cmd("ZREM")), + RequestType::Zrange => Some(cmd("ZRANGE")), + RequestType::Zcard => Some(cmd("ZCARD")), + RequestType::Zcount => Some(cmd("ZCOUNT")), + RequestType::ZIncrBy => Some(cmd("ZINCRBY")), + RequestType::ZScore => Some(cmd("ZSCORE")), + RequestType::Type => Some(cmd("TYPE")), + RequestType::HLen => Some(cmd("HLEN")), + RequestType::Echo => Some(cmd("ECHO")), + RequestType::ZPopMin => Some(cmd("ZPOPMIN")), + RequestType::Strlen => Some(cmd("STRLEN")), + RequestType::Lindex => Some(cmd("LINDEX")), + RequestType::ZPopMax => Some(cmd("ZPOPMAX")), + RequestType::XAck => Some(cmd("XACK")), + RequestType::XAdd => Some(cmd("XADD")), + RequestType::XReadGroup => Some(cmd("XREADGROUP")), + RequestType::XRead => Some(cmd("XREAD")), + RequestType::XGroupCreate => Some(get_two_word_command("XGROUP", "CREATE")), + RequestType::XGroupDestroy => Some(get_two_word_command("XGROUP", "DESTROY")), + RequestType::XTrim => Some(cmd("XTRIM")), + RequestType::HSetNX => Some(cmd("HSETNX")), + RequestType::SIsMember => Some(cmd("SISMEMBER")), + RequestType::Hvals => Some(cmd("HVALS")), + RequestType::PTTL => Some(cmd("PTTL")), + RequestType::ZRemRangeByRank => Some(cmd("ZREMRANGEBYRANK")), + RequestType::Persist => Some(cmd("PERSIST")), + RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), + RequestType::Time => Some(cmd("TIME")), + RequestType::Zrank => Some(cmd("ZRANK")), + RequestType::Rename => Some(cmd("RENAME")), + RequestType::DBSize => Some(cmd("DBSIZE")), + RequestType::Brpop => Some(cmd("BRPOP")), + RequestType::Hkeys => Some(cmd("HKEYS")), + RequestType::PfAdd => Some(cmd("PFADD")), + RequestType::PfCount => Some(cmd("PFCOUNT")), + RequestType::PfMerge => Some(cmd("PFMERGE")), + RequestType::RPushX => Some(cmd("RPUSHX")), + RequestType::LPushX => Some(cmd("LPUSHX")), + RequestType::Blpop => Some(cmd("BLPOP")), + RequestType::Spop => Some(cmd("SPOP")), + } + } +} diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 82047e32af..fc72b49a46 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -6,8 +6,7 @@ use crate::client::Client; use crate::connection_request::ConnectionRequest; use crate::errors::{error_message, error_type, RequestErrorType}; use crate::redis_request::{ - command, redis_request, Command, RedisRequest, RequestType, Routes, ScriptInvocation, - SlotTypes, Transaction, + command, redis_request, Command, RedisRequest, Routes, ScriptInvocation, SlotTypes, Transaction, }; use crate::response; use crate::response::Response; @@ -21,7 +20,7 @@ use redis::cluster_routing::{ }; use redis::cluster_routing::{ResponsePolicy, Routable}; use redis::RedisError; -use redis::{cmd, Cmd, Value}; +use redis::{Cmd, Value}; use std::cell::Cell; use std::rc::Rc; use std::{env, str}; @@ -257,113 +256,9 @@ async fn write_to_writer(response: Response, writer: &Rc) -> Result<(), } } -fn get_two_word_command(first: &str, second: &str) -> Cmd { - let mut cmd = cmd(first); - cmd.arg(second); - cmd -} - fn get_command(request: &Command) -> Option { - let request_enum = request - .request_type - .enum_value_or(RequestType::InvalidRequest); - match request_enum { - RequestType::InvalidRequest => None, - RequestType::CustomCommand => Some(Cmd::new()), - RequestType::GetString => Some(cmd("GET")), - RequestType::SetString => Some(cmd("SET")), - RequestType::Ping => Some(cmd("PING")), - RequestType::Info => Some(cmd("INFO")), - RequestType::Del => Some(cmd("DEL")), - RequestType::Select => Some(cmd("SELECT")), - RequestType::ConfigGet => Some(get_two_word_command("CONFIG", "GET")), - RequestType::ConfigSet => Some(get_two_word_command("CONFIG", "SET")), - RequestType::ConfigResetStat => Some(get_two_word_command("CONFIG", "RESETSTAT")), - RequestType::ConfigRewrite => Some(get_two_word_command("CONFIG", "REWRITE")), - RequestType::ClientGetName => Some(get_two_word_command("CLIENT", "GETNAME")), - RequestType::ClientGetRedir => Some(get_two_word_command("CLIENT", "GETREDIR")), - RequestType::ClientId => Some(get_two_word_command("CLIENT", "ID")), - RequestType::ClientInfo => Some(get_two_word_command("CLIENT", "INFO")), - RequestType::ClientKill => Some(get_two_word_command("CLIENT", "KILL")), - RequestType::ClientList => Some(get_two_word_command("CLIENT", "LIST")), - RequestType::ClientNoEvict => Some(get_two_word_command("CLIENT", "NO-EVICT")), - RequestType::ClientNoTouch => Some(get_two_word_command("CLIENT", "NO-TOUCH")), - RequestType::ClientPause => Some(get_two_word_command("CLIENT", "PAUSE")), - RequestType::ClientReply => Some(get_two_word_command("CLIENT", "REPLY")), - RequestType::ClientSetInfo => Some(get_two_word_command("CLIENT", "SETINFO")), - RequestType::ClientSetName => Some(get_two_word_command("CLIENT", "SETNAME")), - RequestType::ClientUnblock => Some(get_two_word_command("CLIENT", "UNBLOCK")), - RequestType::ClientUnpause => Some(get_two_word_command("CLIENT", "UNPAUSE")), - RequestType::Expire => Some(cmd("EXPIRE")), - RequestType::HashSet => Some(cmd("HSET")), - RequestType::HashGet => Some(cmd("HGET")), - RequestType::HashDel => Some(cmd("HDEL")), - RequestType::HashExists => Some(cmd("HEXISTS")), - RequestType::MSet => Some(cmd("MSET")), - RequestType::MGet => Some(cmd("MGET")), - RequestType::Incr => Some(cmd("INCR")), - RequestType::IncrBy => Some(cmd("INCRBY")), - RequestType::IncrByFloat => Some(cmd("INCRBYFLOAT")), - RequestType::Decr => Some(cmd("DECR")), - RequestType::DecrBy => Some(cmd("DECRBY")), - RequestType::HashGetAll => Some(cmd("HGETALL")), - RequestType::HashMSet => Some(cmd("HMSET")), - RequestType::HashMGet => Some(cmd("HMGET")), - RequestType::HashIncrBy => Some(cmd("HINCRBY")), - RequestType::HashIncrByFloat => Some(cmd("HINCRBYFLOAT")), - RequestType::LPush => Some(cmd("LPUSH")), - RequestType::LPop => Some(cmd("LPOP")), - RequestType::RPush => Some(cmd("RPUSH")), - RequestType::RPop => Some(cmd("RPOP")), - RequestType::LLen => Some(cmd("LLEN")), - RequestType::LRem => Some(cmd("LREM")), - RequestType::LRange => Some(cmd("LRANGE")), - RequestType::LTrim => Some(cmd("LTRIM")), - RequestType::SAdd => Some(cmd("SADD")), - RequestType::SRem => Some(cmd("SREM")), - RequestType::SMembers => Some(cmd("SMEMBERS")), - RequestType::SCard => Some(cmd("SCARD")), - RequestType::PExpireAt => Some(cmd("PEXPIREAT")), - RequestType::PExpire => Some(cmd("PEXPIRE")), - RequestType::ExpireAt => Some(cmd("EXPIREAT")), - RequestType::Exists => Some(cmd("EXISTS")), - RequestType::Unlink => Some(cmd("UNLINK")), - RequestType::TTL => Some(cmd("TTL")), - RequestType::Zadd => Some(cmd("ZADD")), - RequestType::Zrem => Some(cmd("ZREM")), - RequestType::Zrange => Some(cmd("ZRANGE")), - RequestType::Zcard => Some(cmd("ZCARD")), - RequestType::Zcount => Some(cmd("ZCOUNT")), - RequestType::ZIncrBy => Some(cmd("ZINCRBY")), - RequestType::ZScore => Some(cmd("ZSCORE")), - RequestType::Type => Some(cmd("TYPE")), - RequestType::HLen => Some(cmd("HLEN")), - RequestType::Echo => Some(cmd("ECHO")), - RequestType::ZPopMin => Some(cmd("ZPOPMIN")), - RequestType::Strlen => Some(cmd("STRLEN")), - RequestType::Lindex => Some(cmd("LINDEX")), - RequestType::ZPopMax => Some(cmd("ZPOPMAX")), - RequestType::XAck => Some(cmd("XACK")), - RequestType::XAdd => Some(cmd("XADD")), - RequestType::XReadGroup => Some(cmd("XREADGROUP")), - RequestType::XRead => Some(cmd("XREAD")), - RequestType::XGroupCreate => Some(get_two_word_command("XGROUP", "CREATE")), - RequestType::XGroupDestroy => Some(get_two_word_command("XGROUP", "DESTROY")), - RequestType::XTrim => Some(cmd("XTRIM")), - RequestType::HSetNX => Some(cmd("HSETNX")), - RequestType::SIsMember => Some(cmd("SISMEMBER")), - RequestType::Hvals => Some(cmd("HVALS")), - RequestType::PTTL => Some(cmd("PTTL")), - RequestType::ZRemRangeByRank => Some(cmd("ZREMRANGEBYRANK")), - RequestType::Persist => Some(cmd("PERSIST")), - RequestType::ZRemRangeByScore => Some(cmd("ZREMRANGEBYSCORE")), - RequestType::Time => Some(cmd("TIME")), - RequestType::Zrank => Some(cmd("ZRANK")), - RequestType::Rename => Some(cmd("RENAME")), - RequestType::DBSize => Some(cmd("DBSIZE")), - RequestType::Brpop => Some(cmd("BRPOP")), - RequestType::Blpop => Some(cmd("BLPOP")), - } + let request_type: crate::request_type::RequestType = request.request_type.into(); + request_type.get_command() } fn get_redis_command(command: &Command) -> Result { diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index fdc99cc981..8c0a0c65a6 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -6,6 +6,8 @@ import static glide.utils.ArrayTransformUtils.concatenateArrays; import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; +import static redis_request.RedisRequestOuterClass.RequestType.Blpop; +import static redis_request.RedisRequestOuterClass.RequestType.Brpop; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; @@ -13,6 +15,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Expire; import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; +import static redis_request.RedisRequestOuterClass.RequestType.HLen; +import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -21,12 +25,14 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; @@ -36,10 +42,15 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; +import static redis_request.RedisRequestOuterClass.RequestType.PfCount; +import static redis_request.RedisRequestOuterClass.RequestType.PfMerge; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; +import static redis_request.RedisRequestOuterClass.RequestType.RPushX; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; +import static redis_request.RedisRequestOuterClass.RequestType.SIsMember; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; @@ -47,23 +58,32 @@ import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; +import static redis_request.RedisRequestOuterClass.RequestType.Zrange; +import static redis_request.RedisRequestOuterClass.RequestType.Zrank; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; import glide.api.commands.GenericBaseCommands; import glide.api.commands.HashBaseCommands; +import glide.api.commands.HyperLogLogBaseCommands; import glide.api.commands.ListBaseCommands; import glide.api.commands.SetBaseCommands; import glide.api.commands.SortedSetBaseCommands; -import glide.api.commands.StringCommands; +import glide.api.commands.StreamBaseCommands; +import glide.api.commands.StringBaseCommands; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; +import glide.api.models.commands.RangeOptions; +import glide.api.models.commands.RangeOptions.RangeQuery; +import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.api.models.configuration.BaseClientConfiguration; import glide.api.models.exceptions.RedisException; @@ -93,11 +113,13 @@ public abstract class BaseClient implements AutoCloseable, GenericBaseCommands, - StringCommands, + StringBaseCommands, HashBaseCommands, ListBaseCommands, SetBaseCommands, - SortedSetBaseCommands { + SortedSetBaseCommands, + StreamBaseCommands, + HyperLogLogBaseCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -108,10 +130,10 @@ public abstract class BaseClient /** * Async request for an async (non-blocking) Redis client. * - * @param config Redis client Configuration - * @param constructor Redis client constructor reference - * @param Client type - * @return a Future to connect and return a RedisClient + * @param config Redis client Configuration. + * @param constructor Redis client constructor reference. + * @param Client type. + * @return a Future to connect and return a RedisClient. */ protected static CompletableFuture CreateClient( BaseClientConfiguration config, @@ -169,16 +191,16 @@ protected static CommandManager buildCommandManager(ChannelHandler channelHandle } /** - * Extracts the value from a Redis response message and either throws an exception or returns the - * value as an object of type {@link T}. If isNullable, than also returns null - * . + * Extracts the value from a GLIDE core response message and either throws an + * exception or returns the value as an object of type T. If isNullable, + * than also returns null. * - * @param response Redis protobuf message - * @param classType Parameter {@link T} class type - * @param isNullable Accepts null values in the protobuf message - * @return Response as an object of type {@link T} or null - * @param return type - * @throws RedisException on a type mismatch + * @param response Redis protobuf message. + * @param classType Parameter T class type. + * @param isNullable Accepts null values in the protobuf message. + * @return Response as an object of type T or null. + * @param The return value type. + * @throws RedisException On a type mismatch. */ @SuppressWarnings("unchecked") protected T handleRedisResponse(Class classType, boolean isNullable, Response response) @@ -219,6 +241,10 @@ protected Long handleLongResponse(Response response) throws RedisException { return handleRedisResponse(Long.class, false, response); } + protected Long handleLongOrNullResponse(Response response) throws RedisException { + return handleRedisResponse(Long.class, true, response); + } + protected Double handleDoubleResponse(Response response) throws RedisException { return handleRedisResponse(Double.class, false, response); } @@ -237,8 +263,8 @@ protected Object[] handleArrayOrNullResponse(Response response) throws RedisExce /** * @param response A Protobuf response - * @return A map of String to V - * @param Value type + * @return A map of String to V. + * @param Value type. */ @SuppressWarnings("unchecked") // raw Map cast to Map protected Map handleMapResponse(Response response) throws RedisException { @@ -332,12 +358,32 @@ public CompletableFuture hset( return commandManager.submitNewCommand(HashSet, args, this::handleLongResponse); } + @Override + public CompletableFuture hsetnx( + @NonNull String key, @NonNull String field, @NonNull String value) { + return commandManager.submitNewCommand( + HSetNX, new String[] {key, field, value}, this::handleBooleanResponse); + } + @Override public CompletableFuture hdel(@NonNull String key, @NonNull String[] fields) { String[] args = ArrayUtils.addFirst(fields, key); return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse); } + @Override + public CompletableFuture hlen(@NonNull String key) { + return commandManager.submitNewCommand(HLen, new String[] {key}, this::handleLongResponse); + } + + @Override + public CompletableFuture hvals(@NonNull String key) { + return commandManager.submitNewCommand( + Hvals, + new String[] {key}, + response -> castArray(handleArrayResponse(response), String.class)); + } + @Override public CompletableFuture hmget(@NonNull String key, @NonNull String[] fields) { String[] arguments = ArrayUtils.addFirst(fields, key); @@ -444,6 +490,12 @@ public CompletableFuture sadd(@NonNull String key, @NonNull String[] membe return commandManager.submitNewCommand(SAdd, arguments, this::handleLongResponse); } + @Override + public CompletableFuture sismember(@NonNull String key, @NonNull String member) { + return commandManager.submitNewCommand( + SIsMember, new String[] {key, member}, this::handleBooleanResponse); + } + @Override public CompletableFuture srem(@NonNull String key, @NonNull String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); @@ -643,6 +695,32 @@ public CompletableFuture zscore(@NonNull String key, @NonNull String mem ZScore, new String[] {key, member}, this::handleDoubleOrNullResponse); } + @Override + public CompletableFuture zrank(@NonNull String key, @NonNull String member) { + return commandManager.submitNewCommand( + Zrank, new String[] {key, member}, this::handleLongOrNullResponse); + } + + @Override + public CompletableFuture zrankWithScore(@NonNull String key, @NonNull String member) { + return commandManager.submitNewCommand( + Zrank, new String[] {key, member, WITH_SCORE_REDIS_API}, this::handleArrayOrNullResponse); + } + + @Override + public CompletableFuture xadd(@NonNull String key, @NonNull Map values) { + return xadd(key, values, StreamAddOptions.builder().build()); + } + + @Override + public CompletableFuture xadd( + @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) { + String[] arguments = + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), convertMapToKeyValueStringArray(values)); + return commandManager.submitNewCommand(XAdd, arguments, this::handleStringOrNullResponse); + } + @Override public CompletableFuture pttl(@NonNull String key) { return commandManager.submitNewCommand(PTTL, new String[] {key}, this::handleLongResponse); @@ -658,4 +736,78 @@ public CompletableFuture persist(@NonNull String key) { public CompletableFuture type(@NonNull String key) { return commandManager.submitNewCommand(Type, new String[] {key}, this::handleStringResponse); } + + @Override + public CompletableFuture blpop(@NonNull String[] keys, double timeout) { + String[] arguments = ArrayUtils.add(keys, Double.toString(timeout)); + return commandManager.submitNewCommand( + Blpop, arguments, response -> castArray(handleArrayOrNullResponse(response), String.class)); + } + + @Override + public CompletableFuture brpop(@NonNull String[] keys, double timeout) { + String[] arguments = ArrayUtils.add(keys, Double.toString(timeout)); + return commandManager.submitNewCommand( + Brpop, arguments, response -> castArray(handleArrayOrNullResponse(response), String.class)); + } + + @Override + public CompletableFuture rpushx(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(RPushX, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture lpushx(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(LPushX, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture zrange( + @NonNull String key, @NonNull RangeQuery rangeQuery, boolean reverse) { + String[] arguments = RangeOptions.createZrangeArgs(key, rangeQuery, reverse, false); + + return commandManager.submitNewCommand( + Zrange, + arguments, + response -> castArray(handleArrayOrNullResponse(response), String.class)); + } + + @Override + public CompletableFuture zrange(@NonNull String key, @NonNull RangeQuery rangeQuery) { + return this.zrange(key, rangeQuery, false); + } + + @Override + public CompletableFuture> zrangeWithScores( + @NonNull String key, @NonNull ScoredRangeQuery rangeQuery, boolean reverse) { + String[] arguments = RangeOptions.createZrangeArgs(key, rangeQuery, reverse, true); + + return commandManager.submitNewCommand(Zrange, arguments, this::handleMapResponse); + } + + @Override + public CompletableFuture> zrangeWithScores( + @NonNull String key, @NonNull ScoredRangeQuery rangeQuery) { + return this.zrangeWithScores(key, rangeQuery, false); + } + + @Override + public CompletableFuture pfadd(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(PfAdd, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture pfcount(@NonNull String[] keys) { + return commandManager.submitNewCommand(PfCount, keys, this::handleLongResponse); + } + + @Override + public CompletableFuture pfmerge( + @NonNull String destination, @NonNull String[] sourceKeys) { + String[] arguments = ArrayUtils.addFirst(sourceKeys, destination); + return commandManager.submitNewCommand(PfMerge, arguments, this::handleStringResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java index e74df9da45..4cfc991e7c 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java @@ -6,14 +6,15 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands for the "Connection Management" group for cluster clients. + * Supports commands for the "Connection Management" group for a cluster client. * * @see Connection Management Commands */ public interface ConnectionManagementClusterCommands { /** - * Ping the Redis server. The command will be routed to all primaries. + * Pings the Redis server.
+ * The command will be routed to all primary nodes. * * @see redis.io for details. * @return String with "PONG". @@ -26,7 +27,8 @@ public interface ConnectionManagementClusterCommands { CompletableFuture ping(); /** - * Ping the Redis server. The command will be routed to all primaries. + * Pings the Redis server.
+ * The command will be routed to all primary nodes. * * @see redis.io for details. * @param message The server will respond with a copy of the message. @@ -40,7 +42,7 @@ public interface ConnectionManagementClusterCommands { CompletableFuture ping(String message); /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. * @param route Specifies the routing configuration for the command. The client will route the @@ -55,7 +57,7 @@ public interface ConnectionManagementClusterCommands { CompletableFuture ping(Route route); /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. * @param message The ping argument that will be returned. diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java index 8b53417fd0..10d5620eb9 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java @@ -4,14 +4,14 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Connection Management" group for standalone clients. + * Supports commands and transactions for the "Connection Management" group for a standalone client. * * @see Connection Management Commands */ public interface ConnectionManagementCommands { /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. * @return String with "PONG". @@ -24,7 +24,7 @@ public interface ConnectionManagementCommands { CompletableFuture ping(); /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. * @param message The server will respond with a copy of the message. diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index a1aadc31ad..620d3412ab 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -7,8 +7,8 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Generic Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "Generic Commands" group for standalone and cluster + * clients. * * @see Generic Commands */ @@ -24,7 +24,7 @@ public interface GenericBaseCommands { * @example *
{@code
      * Long num = client.del(new String[] {"key1", "key2"}).get();
-     * assert num == 2l;
+     * assert num == 2L;
      * }
*/ CompletableFuture del(String[] keys); @@ -263,8 +263,8 @@ CompletableFuture pexpireAt( * if key exists but has no associated expire. * @example *
{@code
-     * Long timeRemaining = client.ttl("my_key").get()
-     * assert timeRemaining == 3600L //Indicates that "my_key" has a remaining time to live of 3600 seconds.
+     * Long timeRemaining = client.ttl("my_key").get();
+     * assert timeRemaining == 3600L; //Indicates that "my_key" has a remaining time to live of 3600 seconds.
      *
      * Long timeRemaining = client.ttl("nonexistent_key").get();
      * assert timeRemaining == -2L; //Returns -2 for a non-existing key.
diff --git a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
index 57505c1794..2e9812da73 100644
--- a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
@@ -8,7 +8,7 @@
 import java.util.concurrent.CompletableFuture;
 
 /**
- * Supports commands for the "Generic Commands" group for cluster clients.
+ * Supports commands for the "Generic Commands" group for a cluster client.
  *
  * @see Generic Commands
  */
@@ -16,7 +16,7 @@ public interface GenericClusterCommands {
 
     /**
      * Executes a single command, without checking inputs. Every part of the command, including
-     * subcommands, should be added as a separate value in {@code args}.
+     * subcommands, should be added as a separate value in args.
      *
      * 

The command will be routed to all primaries. * @@ -37,7 +37,7 @@ public interface GenericClusterCommands { /** * Executes a single command, without checking inputs. Every part of the command, including - * subcommands, should be added as a separate value in {@code args}. + * subcommands, should be added as a separate value in args. * *

Client will route the command to the nodes defined by route. * @@ -61,7 +61,7 @@ public interface GenericClusterCommands { CompletableFuture> customCommand(String[] args, Route route); /** - * Execute a transaction by processing the queued commands. + * Executes a transaction by processing the queued commands. * *

The transaction will be routed to the slot owner of the first key found in the transaction. * If no key is found, the command will be sent to a random node. @@ -88,7 +88,7 @@ public interface GenericClusterCommands { CompletableFuture exec(ClusterTransaction transaction); /** - * Execute a transaction by processing the queued commands. + * Executes a transaction by processing the queued commands. * * @see redis.io for details on Redis * Transactions. diff --git a/java/client/src/main/java/glide/api/commands/GenericCommands.java b/java/client/src/main/java/glide/api/commands/GenericCommands.java index 2da88c4713..e4bde9ce06 100644 --- a/java/client/src/main/java/glide/api/commands/GenericCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericCommands.java @@ -5,7 +5,7 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Generic Commands" group for standalone clients. + * Supports commands and transactions for the "Generic Commands" group for a standalone client. * * @see Generic Commands */ @@ -13,30 +13,27 @@ public interface GenericCommands { /** * Executes a single command, without checking inputs. Every part of the command, including - * subcommands, should be added as a separate value in args. + * subcommands, should be added as a separate value in args. * + * @param args Arguments for the custom command. + * @return Response from Redis containing an Object. * @remarks This function should only be used for single-response commands. Commands that don't * return response (such as SUBSCRIBE), or that return potentially more than a single * response (such as XREAD), or that change the client's behavior (such as entering * pub/sub mode on RESP2 connections) shouldn't be called using * this function. - * @example Returns a list of all pub/sub clients: - *

-     * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get();
-     * 
- * - * @param args Arguments for the custom command. - * @return Response from Redis containing an Object. * @example *
{@code
-     * Object response = (String) client.customCommand(new String[] {"ping", "GLIDE"}).get()
+     * Object response = (String) client.customCommand(new String[] {"ping", "GLIDE"}).get();
      * assert ((String) response).equals("GLIDE");
+     * // Get a list of all pub/sub clients:
+     * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get();
      * }
*/ CompletableFuture customCommand(String[] args); /** - * Execute a transaction by processing the queued commands. + * Executes a transaction by processing the queued commands. * * @see redis.io for details on Redis * Transactions. diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java index 8007106a16..5f4506cd24 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -5,8 +5,8 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Hash Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "Hash Commands" group for standalone and cluster + * clients. * * @see Hash Commands */ @@ -47,6 +47,29 @@ public interface HashBaseCommands { */ CompletableFuture hset(String key, Map fieldValueMap); + /** + * Sets field in the hash stored at key to value, only if + * field does not yet exist.
+ * If key does not exist, a new key holding a hash is created.
+ * If field already exists, this operation has no effect. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to set the value for. + * @param value The value to set. + * @return true if the field was set, false if the field already existed + * and was not set. + * @example + *
{@code
+     * Boolean payload1 = client.hsetnx("myHash", "field", "value").get();
+     * assert payload1; // Indicates that the field "field" was set successfully in the hash "myHash".
+     *
+     * Boolean payload2 = client.hsetnx("myHash", "field", "newValue").get();
+     * assert !payload2; // Indicates that the field "field" already existed in the hash "myHash" and was not set again.
+     * }
+ */ + CompletableFuture hsetnx(String key, String field, String value); + /** * Removes the specified fields from the hash stored at key. Specified fields that do * not exist within this hash are ignored. @@ -59,12 +82,45 @@ public interface HashBaseCommands { * If key does not exist, it is treated as an empty hash and it returns 0.
* @example *
{@code
-     * Long num = client.hdel("my_hash", new String[] {}).get("field1", "field2");
+     * Long num = client.hdel("my_hash", new String[] {"field1", "field2"}).get();
      * assert num == 2L; //Indicates that two fields were successfully removed from the hash.
      * }
*/ CompletableFuture hdel(String key, String[] fields); + /** + * Returns the number of fields contained in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return The number of fields in the hash, or 0 when the key does not exist.
+ * If key holds a value that is not a hash, an error is returned. + * @example + *
{@code
+     * Long num1 = client.hlen("myHash").get();
+     * assert num1 == 3L;
+     *
+     * Long num2 = client.hlen("nonExistingKey").get();
+     * assert num2 == 0L;
+     * }
+ */ + CompletableFuture hlen(String key); + + /** + * Returns all values in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return An array of values in the hash, or an empty array when the + * key does not exist. + * @example + *
{@code
+     * String[] values = client.hvals("myHash").get();
+     * assert values.equals(new String[] {"value1", "value2", "value3"}); // Returns all the values stored in the hash "myHash".
+     * }
+ */ + CompletableFuture hvals(String key); + /** * Returns the values associated with the specified fields in the hash stored at key. * @@ -142,7 +198,7 @@ public interface HashBaseCommands { CompletableFuture hincrBy(String key, String field, long amount); /** - * Increment the string representing a floating point number stored at field in the + * Increments the string representing a floating point number stored at field in the * hash stored at key by increment. By using a negative increment value, the value * stored at field in the hash stored at key is decremented. If * field or key does not exist, it is set to 0 before performing the @@ -154,7 +210,7 @@ public interface HashBaseCommands { * value. * @param amount The amount by which to increment or decrement the field's value. Use a negative * value to decrement. - * @returns The value of field in the hash stored at key after the + * @return The value of field in the hash stored at key after the * increment or decrement. * @example *
{@code
diff --git a/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java
new file mode 100644
index 0000000000..97e4cd4cbf
--- /dev/null
+++ b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java
@@ -0,0 +1,75 @@
+/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
+package glide.api.commands;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Supports commands and transactions for the "HyperLogLog Commands" group for standalone and
+ * cluster clients.
+ *
+ * @see HyperLogLog Commands
+ */
+public interface HyperLogLogBaseCommands {
+
+    /**
+     * Adds all elements to the HyperLogLog data structure stored at the specified key.
+     * 
+ * Creates a new structure if the key does not exist. + * + *

When no elements are provided, and key exists and is a + * HyperLogLog, then no operation is performed. If key does not exist, then the + * HyperLogLog structure is created. + * + * @see redis.io for details. + * @param key The key of the HyperLogLog data structure to add elements into. + * @param elements An array of members to add to the HyperLogLog stored at key. + * @return If the HyperLogLog is newly created, or if the HyperLogLog approximated cardinality is + * altered, then returns 1. Otherwise, returns 0. + * @example + *

{@code
+     * Long result = client.pfadd("hll_1", new String[] { "a", "b", "c" }).get();
+     * assert result == 1L; // A data structure was created or modified
+     *
+     * result = client.pfadd("hll_2", new String[0]).get();
+     * assert result == 1L; // A new empty data structure was created
+     * }
+ */ + CompletableFuture pfadd(String key, String[] elements); + + /** + * Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or + * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily. + * + * @see redis.io for details. + * @param keys The keys of the HyperLogLog data structures to be analyzed. + * @return The approximated cardinality of given HyperLogLog data structures.
+ * The cardinality of a key that does not exist is 0. + * @example + *
{@code
+     * Long result = client.pfcount("hll_1", "hll_2").get();
+     * assert result == 42L; // Count of unique elements in multiple data structures
+     * }
+ */ + CompletableFuture pfcount(String[] keys); + + /** + * Merges multiple HyperLogLog values into a unique value.
+ * If the destination variable exists, it is treated as one of the source HyperLogLog data sets, + * otherwise a new HyperLogLog is created. + * + * @see redis.io for details. + * @param destination The key of the destination HyperLogLog where the merged data sets will be + * stored. + * @param sourceKeys The keys of the HyperLogLog structures to be merged. + * @return OK. + * @example + *
{@code
+     * String response = client.pfmerge("new_HLL", "old_HLL_1", "old_HLL_2").get();
+     * assert response.equals("OK"); // new HyperLogLog data set was created with merged content of old ones
+     *
+     * String response = client.pfmerge("old_HLL_1", "old_HLL_2", "old_HLL_3").get();
+     * assert response.equals("OK"); // content of existing HyperLogLogs was merged into existing variable
+     * }
+ */ + CompletableFuture pfmerge(String destination, String[] sourceKeys); +} diff --git a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java index 64ead64e59..4483133ec5 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -4,8 +4,8 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "List Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "List Commands" group for standalone and cluster + * clients. * * @see List Commands */ @@ -15,12 +15,12 @@ public interface ListBaseCommands { * Inserts all the specified values at the head of the list stored at key. * elements are inserted one after the other to the head of the list, from the leftmost * element to the rightmost element. If key does not exist, it is created as an empty - * list before performing the push operations. + * list before performing the push operation. * * @see redis.io for details. * @param key The key of the list. * @param elements The elements to insert at the head of the list stored at key. - * @return The length of the list after the push operations. + * @return The length of the list after the push operation. * @example *
{@code
      * Long pushCount1 = client.lpush("my_list", new String[] {"value1", "value2"}).get();
@@ -38,8 +38,8 @@ public interface ListBaseCommands {
      *
      * @see redis.io for details.
      * @param key The key of the list.
-     * @return The value of the first element. 
- * If key does not exist, null will be returned.
+ * @return The value of the first element.
+ * If key does not exist, null will be returned. * @example *
{@code
      * String value1 = client.lpop("my_list").get();
@@ -59,7 +59,7 @@ public interface ListBaseCommands {
      * @param key The key of the list.
      * @param count The count of the elements to pop from the list.
      * @return An array of the popped elements will be returned depending on the list's length.
- * If key does not exist, null will be returned.
+ * If key does not exist, null will be returned. * @example *
{@code
      * String[] values1 = client.lpopCount("my_list", 2).get();
@@ -73,10 +73,11 @@ public interface ListBaseCommands {
 
     /**
      * Returns the specified elements of the list stored at key.
- * The offsets start and end are zero-based indexes, with 0 being the - * first element of the list, 1 being the next element and so on. These offsets can also be - * negative numbers indicating offsets starting at the end of the list, with -1 being the last - * element of the list, -2 being the penultimate, and so on. + * The offsets start and end are zero-based indexes, with 0 + * being the first element of the list, 1 being the next element and so on. These + * offsets can also be negative numbers indicating offsets starting at the end of the list, with + * -1 being the last element of the list, -2 being the penultimate, and + * so on. * * @see redis.io for details. * @param key The key of the list. @@ -87,17 +88,17 @@ public interface ListBaseCommands { * end, an empty array will be returned.
* If end exceeds the actual end of the list, the range will stop at the actual * end of the list.
- * If key does not exist an empty array will be returned.
+ * If key does not exist an empty array will be returned. * @example *
{@code
-     * String[] payload = lient.lrange("my_list", 0, 2).get()
-     * assert payload.equals(new String[] {"value1", "value2", "value3"})
+     * String[] payload = lient.lrange("my_list", 0, 2).get();
+     * assert payload.equals(new String[] {"value1", "value2", "value3"});
      *
-     * String[] payload = client.lrange("my_list", -2, -1).get()
-     * assert payload.equals(new String[] {"value2", "value3"})
+     * String[] payload = client.lrange("my_list", -2, -1).get();
+     * assert payload.equals(new String[] {"value2", "value3"});
      *
-     * String[] payload = client.lrange("non_exiting_key", 0, 2).get()
-     * assert payload.equals(new String[] {})
+     * String[] payload = client.lrange("non_exiting_key", 0, 2).get();
+     * assert payload.equals(new String[] {});
      * }
*/ CompletableFuture lrange(String key, long start, long end); @@ -134,7 +135,8 @@ public interface ListBaseCommands { * @see redis.io for details. * @param key The key of the list. * @return The length of the list at key.
- * If key does not exist, it is interpreted as an empty list and 0 is returned. + * If key does not exist, it is interpreted as an empty list and 0 + * is returned. * @example *
{@code
      * Long lenList = client.llen("my_list").get();
@@ -151,14 +153,14 @@ public interface ListBaseCommands {
      * If count is negative: Removes elements equal to element moving from
      * tail to head.
* If count is 0 or count is greater than the occurrences of elements - * equal to element, it removes all elements equal to element.
+ * equal to element, it removes all elements equal to element. * * @see redis.io for details. * @param key The key of the list. * @param count The count of the occurrences of elements equal to element to remove. * @param element The element to remove from the list. * @return The number of the removed elements.
- * If key does not exist, 0 is returned.
+ * If key does not exist, 0 is returned. * @example *
{@code
      * Long num = client.rem("my_list", 2, "value").get();
@@ -171,19 +173,19 @@ public interface ListBaseCommands {
      * Inserts all the specified values at the tail of the list stored at key.
* elements are inserted one after the other to the tail of the list, from the * leftmost element to the rightmost element. If key does not exist, it is created as - * an empty list before performing the push operations. + * an empty list before performing the push operation. * * @see redis.io for details. * @param key The key of the list. * @param elements The elements to insert at the tail of the list stored at key. - * @return The length of the list after the push operations. + * @return The length of the list after the push operation. * @example *
{@code
-     * Long pushCount1 = client.rpush("my_list", new String[] {"value1", "value2"}).get()
-     * assert pushCount1 == 2L
+     * Long pushCount1 = client.rpush("my_list", new String[] {"value1", "value2"}).get();
+     * assert pushCount1 == 2L;
      *
-     * Long pushCount2 = client.rpush("nonexistent_list", new String[] {"new_value"}).get()
-     * assert pushCount2 == 1L
+     * Long pushCount2 = client.rpush("nonexistent_list", new String[] {"new_value"}).get();
+     * assert pushCount2 == 1L;
      * }
*/ CompletableFuture rpush(String key, String[] elements); @@ -195,7 +197,7 @@ public interface ListBaseCommands { * @see redis.io for details. * @param key The key of the list. * @return The value of the last element.
- * If key does not exist, null will be returned.
+ * If key does not exist, null will be returned. * @example *
{@code
      * String value1 = client.rpop("my_list").get();
@@ -212,9 +214,10 @@ public interface ListBaseCommands {
      * depending on the list's length.
      *
      * @see redis.io for details.
+     * @param key The key of the list.
      * @param count The count of the elements to pop from the list.
-     * @returns An array of popped elements will be returned depending on the list's length.
- * If key does not exist, null will be returned.
+ * @return An array of popped elements will be returned depending on the list's length.
+ * If key does not exist, null will be returned. * @example *
{@code
      * String[] values1 = client.rpopCount("my_list", 2).get();
@@ -225,4 +228,84 @@ public interface ListBaseCommands {
      * }
*/ CompletableFuture rpopCount(String key, long count); + + /** + * Pops an element from the head of the first list that is non-empty, with the given keys being + * checked in the order that they are given.
+ * Blocks the connection when there are no elements to pop from any of the given lists. + * + * @see redis.io for details. + * @apiNote BLPOP is a client blocking command, see Blocking + * Commands for more details and best practices. + * @param keys The keys of the lists to pop from. + * @param timeout The number of seconds to wait for a blocking BLPOP operation to + * complete. A value of 0 will block indefinitely. + * @return An array containing the key from which the element was popped + * and the value of the popped element, formatted as [key, value]. + * If no element could be popped and the timeout expired, returns null. + * @example + *
{@code
+     * String[] response = client.blpop(["list1", "list2"], 0.5).get();
+     * assert response[0].equals("list1");
+     * assert response[1].equals("element");
+     * }
+ */ + CompletableFuture blpop(String[] keys, double timeout); + + /** + * Pops an element from the tail of the first list that is non-empty, with the given keys being + * checked in the order that they are given.
+ * Blocks the connection when there are no elements to pop from any of the given lists. + * + * @see redis.io for details. + * @apiNote BRPOP is a client blocking command, see Blocking + * Commands for more details and best practices. + * @param keys The keys of the lists to pop from. + * @param timeout The number of seconds to wait for a blocking BRPOP operation to + * complete. A value of 0 will block indefinitely. + * @return An array containing the key from which the element was popped + * and the value of the popped element, formatted as [key, value]. + * If no element could be popped and the timeout expired, returns null. + * @example + *
{@code
+     * String[] response = client.brpop(["list1", "list2"], 0.5).get();
+     * assert response[0].equals("list1");
+     * assert response[1].equals("element");
+     * }
+ */ + CompletableFuture brpop(String[] keys, double timeout); + + /** + * Inserts specified values at the tail of the list, only if key already + * exists and holds a list. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the tail of the list stored at key. + * @return The length of the list after the push operation. + * @example + *
{@code
+     * Long listLength = client.rpushx("my_list", new String[] {"value1", "value2"}).get();
+     * assert listLength >= 2L;
+     * }
+ */ + CompletableFuture rpushx(String key, String[] elements); + + /** + * Inserts specified values at the head of the list, only if key already + * exists and holds a list. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the head of the list stored at key. + * @return The length of the list after the push operation. + * @example + *
{@code
+     * Long listLength = client.lpushx("my_list", new String[] {"value1", "value2"}).get();
+     * assert listLength >= 2L;
+     * }
+ */ + CompletableFuture lpushx(String key, String[] elements); } diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java index d5614bbdac..b9c3865529 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java @@ -9,16 +9,16 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Set Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "Server Management Commands" group for a cluster + * client. * - * @see Set Commands + * @see Server Management Commands */ public interface ServerManagementClusterCommands { /** - * Get information and statistics about the Redis server using the {@link Section#DEFAULT} option. - * The command will be routed to all primary nodes. + * Gets information and statistics about the Redis server using the {@link Section#DEFAULT} + * option. The command will be routed to all primary nodes. * * @see redis.io for details. * @return Response from Redis cluster with a Map{@literal } with @@ -35,7 +35,7 @@ public interface ServerManagementClusterCommands { CompletableFuture> info(); /** - * Get information and statistics about the Redis server. If no argument is provided, so the + * Gets information and statistics about the Redis server. If no argument is provided, so the * {@link Section#DEFAULT} option is assumed. * * @see redis.io for details. @@ -57,7 +57,7 @@ public interface ServerManagementClusterCommands { CompletableFuture> info(Route route); /** - * Get information and statistics about the Redis server. The command will be routed to all + * Gets information and statistics about the Redis server. The command will be routed to all * primary nodes. * * @see redis.io for details. @@ -79,7 +79,7 @@ public interface ServerManagementClusterCommands { CompletableFuture> info(InfoOptions options); /** - * Get information and statistics about the Redis server. + * Gets information and statistics about the Redis server. * * @see redis.io for details. * @param options A list of {@link InfoOptions.Section} values specifying which sections of diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java index 1d3e4e3b78..177be2bbb0 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java @@ -7,14 +7,15 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Server Management" group for standalone clients. + * Supports commands and transactions for the "Server Management" group for a standalone client. * * @see Server Management Commands */ public interface ServerManagementCommands { /** - * Get information and statistics about the Redis server using the {@link Section#DEFAULT} option. + * Gets information and statistics about the Redis server using the {@link Section#DEFAULT} + * option. * * @see redis.io for details. * @return Response from Redis containing a String with the information for the @@ -44,7 +45,7 @@ public interface ServerManagementCommands { CompletableFuture info(InfoOptions options); /** - * Change the currently selected Redis database. + * Changes the currently selected Redis database. * * @see redis.io for details. * @param index The index of the database to select. diff --git a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java index f84f01ca96..9ddd6766da 100644 --- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java @@ -5,15 +5,15 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Set Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "Set Commands" group for standalone and cluster + * clients. * * @see Set Commands */ public interface SetBaseCommands { /** - * Add specified members to the set stored at key. Specified members that are already - * a member of this set are ignored. + * Adds specified members to the set stored at key. Specified members that are + * already a member of this set are ignored. * * @see redis.io for details. * @param key The key where members will be added to its set. @@ -30,7 +30,7 @@ public interface SetBaseCommands { CompletableFuture sadd(String key, String[] members); /** - * Remove specified members from the set stored at key. Specified members that are + * Removes specified members from the set stored at key. Specified members that are * not a member of this set are ignored. * * @see redis.io for details. @@ -38,7 +38,7 @@ public interface SetBaseCommands { * @param members A list of members to remove from the set stored at key. * @return The number of members that were removed from the set, excluding non-existing members. * @remarks If key does not exist, it is treated as an empty set and this command - * returns 0. + * returns 0. * @example *
{@code
      * Long result = client.srem("my_set", new String[]{"member1", "member2"}).get();
@@ -48,7 +48,7 @@ public interface SetBaseCommands {
     CompletableFuture srem(String key, String[] members);
 
     /**
-     * Retrieve all the members of the set value stored at key.
+     * Retrieves all the members of the set value stored at key.
      *
      * @see redis.io for details.
      * @param key The key from which to retrieve the set members.
@@ -63,7 +63,7 @@ public interface SetBaseCommands {
     CompletableFuture> smembers(String key);
 
     /**
-     * Retrieve the set cardinality (number of elements) of the set stored at key.
+     * Retrieves the set cardinality (number of elements) of the set stored at key.
      *
      * @see redis.io for details.
      * @param key The key from which to retrieve the number of set members.
@@ -75,4 +75,24 @@ public interface SetBaseCommands {
      * }
*/ CompletableFuture scard(String key); + + /** + * Returns if member is a member of the set stored at key. + * + * @see redis.io for details. + * @param key The key of the set. + * @param member The member to check for existence in the set. + * @return true if the member exists in the set, false otherwise. If + * key doesn't exist, it is treated as an empty set and the command + * returns false. + * @example + *
{@code
+     * Boolean payload1 = client.sismember("mySet", "member1").get();
+     * assert payload1; // Indicates that "member1" exists in the set "mySet".
+     *
+     * Boolean payload2 = client.sismember("mySet", "nonExistingMember").get();
+     * assert !payload2; // Indicates that "nonExistingMember" does not exist in the set "mySet".
+     * }
+ */ + CompletableFuture sismember(String key, String member); } diff --git a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java index 726c0ce1b9..d4a9e5d4ea 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -1,17 +1,24 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.commands; +import glide.api.models.commands.RangeOptions.RangeByIndex; +import glide.api.models.commands.RangeOptions.RangeByLex; +import glide.api.models.commands.RangeOptions.RangeByScore; +import glide.api.models.commands.RangeOptions.RangeQuery; +import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.ZaddOptions; import java.util.Map; import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "Sorted Set Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "Sorted Set Commands" group for standalone and cluster + * clients. * * @see Sorted Set Commands */ public interface SortedSetBaseCommands { + public static final String WITH_SCORES_REDIS_API = "WITHSCORES"; + public static final String WITH_SCORE_REDIS_API = "WITHSCORE"; /** * Adds members with their scores to the sorted set stored at key.
@@ -23,14 +30,16 @@ public interface SortedSetBaseCommands { * @param options The Zadd options. * @param changed Modify the return value from the number of new elements added, to the total * number of elements changed. - * @return The number of elements added to the sorted set.
+ * @return The number of elements added to the sorted set.
* If changed is set, returns the number of elements updated in the sorted set. * @example *
{@code
-     * Long num = client.zadd("mySortedSet", Map.of("member1", 10.5, "member2", 8.2), ZaddOptions.builder().build(), false).get();
+     * ZaddOptions options = ZaddOptions.builder().conditionalChange(ONLY_IF_DOES_NOT_EXIST).build();
+     * Long num = client.zadd("mySortedSet", Map.of("member1", 10.5, "member2", 8.2), options, false).get();
      * assert num == 2L; // Indicates that two elements have been added or updated in the sorted set "mySortedSet".
      *
-     * Long num = client.zadd("existingSortedSet", Map.of("member1", 15.0, "member2", 5.5), ZaddOptions.builder().conditionalChange(ZaddOptions.ConditionalChange.ONLY_IF_EXISTS).build(), false).get();
+     * options = ZaddOptions.builder().conditionalChange(ONLY_IF_EXISTS).build();
+     * Long num = client.zadd("existingSortedSet", Map.of("member1", 15.0, "member2", 5.5), options, false).get();
      * assert num == 2L; // Updates the scores of two existing members in the sorted set "existingSortedSet".
      * }
*/ @@ -48,10 +57,12 @@ CompletableFuture zadd( * @return The number of elements added to the sorted set. * @example *
{@code
-     * Long num = client.zadd("mySortedSet", Map.of("member1", 10.5, "member2", 8.2), ZaddOptions.builder().build()).get();
+     * ZaddOptions options = ZaddOptions.builder().conditionalChange(ONLY_IF_DOES_NOT_EXIST).build();
+     * Long num = client.zadd("mySortedSet", Map.of("member1", 10.5, "member2", 8.2), options).get();
      * assert num == 2L; // Indicates that two elements have been added to the sorted set "mySortedSet".
      *
-     * Long num = client.zadd("existingSortedSet", Map.of("member1", 15.0, "member2", 5.5), ZaddOptions.builder().conditionalChange(ZaddOptions.ConditionalChange.ONLY_IF_EXISTS).build()).get();
+     * options = ZaddOptions.builder().conditionalChange(ONLY_IF_EXISTS).build();
+     * Long num = client.zadd("existingSortedSet", Map.of("member1", 15.0, "member2", 5.5), options).get();
      * assert num == 0L; // No new members were added to the sorted set "existingSortedSet".
      * }
*/ @@ -67,9 +78,8 @@ CompletableFuture zadd( * @param membersScoresMap A Map of members to their corresponding scores. * @param changed Modify the return value from the number of new elements added, to the total * number of elements changed. - * @return The number of elements added to the sorted set.
+ * @return The number of elements added to the sorted set.
* If changed is set, returns the number of elements updated in the sorted set. - *
* @example *
{@code
      * Long num = client.zadd("mySortedSet", Map.of("member1", 10.5, "member2", 8.2), true).get();
@@ -98,7 +108,7 @@ CompletableFuture zadd(
      * Increments the score of member in the sorted set stored at key by increment
      * .
* If member does not exist in the sorted set, it is added with - * increment as its score (as if its previous score was 0.0).
+ * increment as its score (as if its previous score was 0.0).
* If key does not exist, a new sorted set with the specified member as its sole * member is created. * @@ -109,13 +119,15 @@ CompletableFuture zadd( * @param options The Zadd options. * @return The score of the member.
* If there was a conflict with the options, the operation aborts and null is - * returned.
+ * returned. * @example *
{@code
-     * Double num = client.zaddIncr("mySortedSet", member, 5.0, ZaddOptions.builder().build()).get();
+     * ZaddOptions options = ZaddOptions.builder().conditionalChange(ONLY_IF_DOES_NOT_EXIST).build();
+     * Double num = client.zaddIncr("mySortedSet", member, 5.0, options).get();
      * assert num == 5.0;
      *
-     * Double num = client.zaddIncr("existingSortedSet", member, 3.0, ZaddOptions.builder().updateOptions(ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT).build()).get();
+     * options = ZaddOptions.builder().updateOptions(SCORE_LESS_THAN_CURRENT).build();
+     * Double num = client.zaddIncr("existingSortedSet", member, 3.0, options).get();
      * assert num == null;
      * }
*/ @@ -126,7 +138,7 @@ CompletableFuture zaddIncr( * Increments the score of member in the sorted set stored at key by increment * .
* If member does not exist in the sorted set, it is added with - * increment as its score (as if its previous score was 0.0).
+ * increment as its score (as if its previous score was 0.0).
* If key does not exist, a new sorted set with the specified member as its sole * member is created. * @@ -279,4 +291,169 @@ CompletableFuture zaddIncr( * }
*/ CompletableFuture zscore(String key, String member); + + /** + * Returns the specified range of elements in the sorted set stored at key.
+ * ZRANGE can perform different types of range queries: by index (rank), by the + * score, or by lexicographical order.
+ * To get the elements with their scores, see {@link #zrangeWithScores}. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the element with the highest + * score. + * @return An array of elements within the specified range. If key does not exist, it + * is treated as an empty sorted set, and the command returns an empty array. + * @example + *
{@code
+     * RangeByScore query1 = new RangeByScore(new ScoreBoundary(10), new ScoreBoundary(20));
+     * String[] payload1 = client.zrange("mySortedSet", query1, true).get(); // Returns members with scores between 10 and 20.
+     * assert payload1.equals(new String[] {'member3', 'member2', 'member1'}); // Returns all members in descending order.
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3));
+     * String[] payload2 = client.zrange("mySortedSet", query2, false).get();
+     * assert payload2.equals(new String[] {'member2', 'member3'}); // Returns members with scores within the range of negative infinity to 3, in ascending order.
+     * }
+ */ + CompletableFuture zrange(String key, RangeQuery rangeQuery, boolean reverse); + + /** + * Returns the specified range of elements in the sorted set stored at key.
+ * ZRANGE can perform different types of range queries: by index (rank), by the + * score, or by lexicographical order.
+ * To get the elements with their scores, see {@link #zrangeWithScores}. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return An of array elements within the specified range. If key does not exist, it + * is treated as an empty sorted set, and the command returns an empty array. + * @example + *
{@code
+     * RangeByIndex query1 = new RangeByIndex(0, -1);
+     * String[] payload1 = client.zrange("mySortedSet",query1).get();
+     * assert payload1.equals(new String[] {'member1', 'member2', 'member3'}); // Returns all members in ascending order.
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3));
+     * String[] payload2 = client.zrange("mySortedSet", query2).get();
+     * assert payload2.equals(new String[] {'member2', 'member3'}); // Returns members with scores within the range of negative infinity to 3, in ascending order.
+     * }
+ */ + CompletableFuture zrange(String key, RangeQuery rangeQuery); + + /** + * Returns the specified range of elements with their scores in the sorted set stored at key + * . Similar to {@link #zrange} but with a WITHSCORE flag. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the element with the highest + * score. + * @return A Map of elements and their scores within the specified range. If + * key does not exist, it is treated as an empty sorted set, and the command returns an + * empty Map. + * @example + *
{@code
+     * RangeByScore query1 = new RangeByScore(new ScoreBoundary(10), new ScoreBoundary(20));
+     * Map payload1 = client.zrangeWithScores("mySortedSet", query1, true).get();
+     * assert payload1.equals(Map.of('member2', 15.2, 'member1', 10.5)); // Returns members with scores between 10 and 20 (inclusive) with their scores.
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3));
+     * Map payload2 = client.zrangeWithScores("mySortedSet", query2, false).get();
+     * assert payload2.equals(Map.of('member4', -2.0, 'member7', 1.5)); // Returns members with with scores within the range of negative infinity to 3, with their scores.
+     * }
+ */ + CompletableFuture> zrangeWithScores( + String key, ScoredRangeQuery rangeQuery, boolean reverse); + + /** + * Returns the specified range of elements with their scores in the sorted set stored at key + * . Similar to {@link #zrange} but with a WITHSCORE flag. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return A Map of elements and their scores within the specified range. If + * key does not exist, it is treated as an empty sorted set, and the command returns an + * empty Map. + * @example + *
{@code
+     * RangeByScore query1 = new RangeByScore(new ScoreBoundary(10), new ScoreBoundary(20));
+     * Map payload1 = client.zrangeWithScores("mySortedSet", query1).get();
+     * assert payload1.equals(Map.of('member1', 10.5, 'member2', 15.2)); // Returns members with scores between 10 and 20 (inclusive) with their scores.
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3));
+     * Map payload2 = client.zrangeWithScores("mySortedSet", query2).get();
+     * assert payload2.equals(Map.of('member4', -2.0, 'member7', 1.5)); // Returns members with with scores within the range of negative infinity to 3, with their scores.
+     * }
+ */ + CompletableFuture> zrangeWithScores(String key, ScoredRangeQuery rangeQuery); + + /** + * Returns the rank of member in the sorted set stored at key, with + * scores ordered from low to high.
+ * To get the rank of member with it's score, see zrankWithScore. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param member The member whose rank is to be retrieved. + * @return The rank of member in the sorted set.
+ * If key doesn't exist, or if member is not present in the set, + * null will be returned. + * @example + *
{@code
+     * Long num1 = client.zrank("mySortedSet", "member2").get();
+     * assert num1 == 3L; // Indicates that "member2" has the second-lowest score in the sorted set "mySortedSet".
+     *
+     * Long num2 = client.zcard("mySortedSet", "nonExistingMember").get();
+     * assert num2 == null; // Indicates that "nonExistingMember" is not present in the sorted set "mySortedSet".
+     * }
+ */ + CompletableFuture zrank(String key, String member); + + /** + * Returns the rank of member in the sorted set stored at key with it's + * score, where scores are ordered from the lowest to highest. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param member The member whose rank is to be retrieved. + * @return An array containing the rank (as Long) and score (as Double) + * of member in the sorted set.
+ * If key doesn't exist, or if member is not present in the set, + * null will be returned. + * @example + *
{@code
+     * Object[] result1 = client.zrankWithScore("mySortedSet", "member2").get();
+     * assert ((Long)result1[0]) == 1L && ((Double)result1[1]) == 6.0; // Indicates that "member2" with score 6.0 has the second-lowest score in the sorted set "mySortedSet".
+     *
+     * Object[] result2 = client.zrankWithScore("mySortedSet", "nonExistingMember").get();
+     * assert num2 == null; // Indicates that "nonExistingMember" is not present in the sorted set "mySortedSet".
+     * }
+ */ + CompletableFuture zrankWithScore(String key, String member); } diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java new file mode 100644 index 0000000000..d3bb55fe91 --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -0,0 +1,51 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands; + +import glide.api.models.commands.StreamAddOptions; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Supports commands and transactions for the "Stream Commands" group for standalone and cluster + * clients. + * + * @see Stream Commands + */ +public interface StreamBaseCommands { + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @return The id of the added entry. + * @example + *
{@code
+     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor").get();
+     * System.out.println("Stream: " + streamId);
+     * }
+ */ + CompletableFuture xadd(String key, Map values); + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @param options Stream add options. + * @return The id of the added entry, or null if {@link StreamAddOptions#makeStream} + * is set to false and no stream with the matching key exists. + * @example + *
{@code
+     * // Option to use the existing stream, or return null if the stream doesn't already exist at "key"
+     * StreamAddOptions options = StreamAddOptions.builder().id("sid").makeStream(Boolean.FALSE).build();
+     * String streamId = client.xadd("key", Map.of("name", "Sara", "surname", "OConnor"), options).get();
+     * if (streamId != null) {
+     *     assert streamId.equals("sid");
+     * }
+     * }
+ */ + CompletableFuture xadd(String key, Map values, StreamAddOptions options); +} diff --git a/java/client/src/main/java/glide/api/commands/StringCommands.java b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java similarity index 77% rename from java/client/src/main/java/glide/api/commands/StringCommands.java rename to java/client/src/main/java/glide/api/commands/StringBaseCommands.java index 99295aae08..2f5177f86a 100644 --- a/java/client/src/main/java/glide/api/commands/StringCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java @@ -8,16 +8,16 @@ import java.util.concurrent.CompletableFuture; /** - * Supports commands and transactions for the "String Commands" group for standalone clients and - * cluster clients. + * Supports commands and transactions for the "String Commands" group for standalone and cluster + * clients. * * @see String Commands */ -public interface StringCommands { +public interface StringBaseCommands { /** - * Get the value associated with the given key, or null if no such value - * exists. + * Gets the value associated with the given key, or null if no such + * value exists. * * @see redis.io for details. * @param key The key to retrieve from the database. @@ -25,17 +25,17 @@ public interface StringCommands { * key as a String. Otherwise, return null. * @example *
{@code
-     * String payload = client.get("key").get();
-     * assert payload.equals("value");
+     * String value = client.get("key").get();
+     * assert value.equals("value");
      *
-     * String payload = client.get("non_existing_key").get();
-     * assert payload.equals(null);
+     * String value = client.get("non_existing_key").get();
+     * assert value.equals(null);
      * }
*/ CompletableFuture get(String key); /** - * Set the given key with the given value. + * Sets the given key with the given value. * * @see redis.io for details. * @param key The key to store. @@ -43,14 +43,14 @@ public interface StringCommands { * @return Response from Redis containing "OK". * @example *
{@code
-     * String payload = client.set("key", "value").get();
-     * assert payload.equals("OK");
+     * String value = client.set("key", "value").get();
+     * assert value.equals("OK");
      * }
*/ CompletableFuture set(String key, String value); /** - * Set the given key with the given value. Return value is dependent on the passed options. + * Sets the given key with the given value. Return value is dependent on the passed options. * * @see redis.io for details. * @param key The key to store. @@ -63,19 +63,15 @@ public interface StringCommands { * is set, return the old value as a String. * @example *
{@code
-     * String payload =
-     *         client.set("key", "value", SetOptions.builder()
-     *                 .conditionalSet(ONLY_IF_EXISTS)
-     *                 .expiry(SetOptions.Expiry.Seconds(5L))
-     *                 .build())
-     *                 .get();
-     * assert payload.equals("OK");
+     * SetOptions options = SetOptions.builder().conditionalSet(ONLY_IF_EXISTS).expiry(Seconds(5L)).build();
+     * String value = client.set("key", "value", options).get();
+     * assert value.equals("OK");
      * }
*/ CompletableFuture set(String key, String value, SetOptions options); /** - * Retrieve the values of multiple keys. + * Retrieves the values of multiple keys. * * @see redis.io for details. * @param keys A list of keys to retrieve values for. @@ -84,28 +80,28 @@ public interface StringCommands { * . * @example *
{@code
-     * String payload = client.mget(new String[] {"key1", "key2"}).get();
-     * assert payload.equals(new String[] {"value1", "value2"});
+     * String values = client.mget(new String[] {"key1", "key2"}).get();
+     * assert values.equals(new String[] {"value1", "value2"});
      * }
*/ CompletableFuture mget(String[] keys); /** - * Set multiple keys to multiple values in a single operation. + * Sets multiple keys to multiple values in a single operation. * * @see redis.io for details. * @param keyValueMap A key-value map consisting of keys and their respective values to set. * @return Always OK. * @example *
{@code
-     * String payload = client.mset(Map.of("key1", "value1", "key2", "value2"}).get();
-     * assert payload.equals("OK"));
+     * String result = client.mset(Map.of("key1", "value1", "key2", "value2"}).get();
+     * assert result.equals("OK"));
      * }
*/ CompletableFuture mset(Map keyValueMap); /** - * Increment the number stored at key by one. If key does not exist, it + * Increments the number stored at key by one. If key does not exist, it * is set to 0 before performing the operation. * * @see redis.io for details. @@ -120,7 +116,7 @@ public interface StringCommands { CompletableFuture incr(String key); /** - * Increment the number stored at key by amount. If key + * Increments the number stored at key by amount. If key * does not exist, it is set to 0 before performing the operation. * * @see redis.io for details. @@ -136,7 +132,7 @@ public interface StringCommands { CompletableFuture incrBy(String key, long amount); /** - * Increment the string representing a floating point number stored at key by + * Increments the string representing a floating point number stored at key by * amount. By using a negative increment value, the result is that the value stored at * key is decremented. If key does not exist, it is set to 0 before * performing the operation. @@ -147,14 +143,14 @@ public interface StringCommands { * @return The value of key after the increment. * @example *
{@code
-     * Long num = client.incrByFloat("key", 0.5).get();
+     * Double num = client.incrByFloat("key", 0.5).get();
      * assert num == 7.5;
      * }
*/ CompletableFuture incrByFloat(String key, double amount); /** - * Decrement the number stored at key by one. If key does not exist, it + * Decrements the number stored at key by one. If key does not exist, it * is set to 0 before performing the operation. * * @see redis.io for details. @@ -169,7 +165,7 @@ public interface StringCommands { CompletableFuture decr(String key); /** - * Decrement the number stored at key by amount. If key + * Decrements the number stored at key by amount. If key * does not exist, it is set to 0 before performing the operation. * * @see redis.io for details. diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index d50210fcb4..7a7a93b1a2 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -1,9 +1,13 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API; +import static glide.api.models.commands.RangeOptions.createZrangeArgs; import static glide.utils.ArrayTransformUtils.concatenateArrays; import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; +import static redis_request.RedisRequestOuterClass.RequestType.Blpop; +import static redis_request.RedisRequestOuterClass.RequestType.Brpop; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.ConfigGet; @@ -19,6 +23,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Expire; import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; +import static redis_request.RedisRequestOuterClass.RequestType.HLen; +import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -27,6 +33,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -34,6 +41,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; @@ -43,11 +51,16 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; +import static redis_request.RedisRequestOuterClass.RequestType.PfCount; +import static redis_request.RedisRequestOuterClass.RequestType.PfMerge; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; +import static redis_request.RedisRequestOuterClass.RequestType.RPushX; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; +import static redis_request.RedisRequestOuterClass.RequestType.SIsMember; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; @@ -56,19 +69,28 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; +import static redis_request.RedisRequestOuterClass.RequestType.Zrange; +import static redis_request.RedisRequestOuterClass.RequestType.Zrank; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.InfoOptions.Section; +import glide.api.models.commands.RangeOptions.RangeByIndex; +import glide.api.models.commands.RangeOptions.RangeByLex; +import glide.api.models.commands.RangeOptions.RangeByScore; +import glide.api.models.commands.RangeOptions.RangeQuery; +import glide.api.models.commands.RangeOptions.ScoredRangeQuery; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SetOptions.ConditionalSet; import glide.api.models.commands.SetOptions.SetOptionsBuilder; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import java.util.Map; import lombok.Getter; @@ -101,18 +123,17 @@ public abstract class BaseTransaction> { * Executes a single command, without checking inputs. Every part of the command, including * subcommands, should be added as a separate value in args. * + * @param args Arguments for the custom command. + * @return A response from Redis with an Object. * @remarks This function should only be used for single-response commands. Commands that don't * return response (such as SUBSCRIBE), or that return potentially more than a single * response (such as XREAD), or that change the client's behavior (such as entering * pub/sub mode on RESP2 connections) shouldn't be called using * this function. * @example Returns a list of all pub/sub clients: - *
+     *     
{@code
      * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get();
-     * 
- * - * @param args Arguments for the custom command. - * @return A response from Redis with an Object. + * }
*/ public T customCommand(String[] args) { @@ -135,10 +156,10 @@ public T echo(@NonNull String message) { } /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. - * @return A response from Redis with a String. + * @return Command Response - A response from Redis with a String. */ public T ping() { protobufTransaction.addCommands(buildCommand(Ping)); @@ -146,11 +167,11 @@ public T ping() { } /** - * Ping the Redis server. + * Pings the Redis server. * * @see redis.io for details. * @param msg The ping argument that will be returned. - * @return A response from Redis with a String. + * @return Command Response - A response from Redis with a String. */ public T ping(@NonNull String msg) { ArgsArray commandArgs = buildArgs(msg); @@ -160,10 +181,11 @@ public T ping(@NonNull String msg) { } /** - * Get information and statistics about the Redis server using the {@link Section#DEFAULT} option. + * Gets information and statistics about the Redis server using the {@link Section#DEFAULT} + * option. * * @see redis.io for details. - * @return A response from Redis with a String. + * @return Command Response - A String with server info. */ public T info() { protobufTransaction.addCommands(buildCommand(Info)); @@ -171,13 +193,12 @@ public T info() { } /** - * Get information and statistics about the Redis server. + * Gets information and statistics about the Redis server. * * @see redis.io for details. * @param options A list of {@link Section} values specifying which sections of information to * retrieve. When no parameter is provided, the {@link Section#DEFAULT} option is assumed. - * @return Response from Redis with a String containing the requested {@link - * Section}s. + * @return Command Response - A String containing the requested {@link Section}s. */ public T info(@NonNull InfoOptions options) { ArgsArray commandArgs = buildArgs(options.toArgs()); @@ -202,11 +223,11 @@ public T del(@NonNull String[] keys) { } /** - * Get the value associated with the given key, or null if no such value exists. + * Gets the value associated with the given key, or null if no such value exists. * * @see redis.io for details. * @param key The key to retrieve from the database. - * @return Response from Redis. key exists, returns the value of + * @return Command Response - If key exists, returns the value of * key as a String. Otherwise, return null. */ public T get(@NonNull String key) { @@ -217,12 +238,12 @@ public T get(@NonNull String key) { } /** - * Set the given key with the given value. + * Sets the given key with the given value. * * @see redis.io for details. * @param key The key to store. * @param value The value to store with the given key. - * @return Response from Redis. + * @return Command Response - A response from Redis. */ public T set(@NonNull String key, @NonNull String value) { ArgsArray commandArgs = buildArgs(key, value); @@ -232,14 +253,14 @@ public T set(@NonNull String key, @NonNull String value) { } /** - * Set the given key with the given value. Return value is dependent on the passed options. + * Sets the given key with the given value. Return value is dependent on the passed options. * * @see redis.io for details. * @param key The key to store. * @param value The value to store with the given key. * @param options The Set options. - * @return Response from Redis with a String or null response. The old - * value as a String if {@link SetOptionsBuilder#returnOldValue(boolean)} is set. + * @return Command Response - A String or null response. The old value + * as a String if {@link SetOptionsBuilder#returnOldValue(boolean)} is set. * Otherwise, if the value isn't set because of {@link ConditionalSet#ONLY_IF_EXISTS} or * {@link ConditionalSet#ONLY_IF_DOES_NOT_EXIST} conditions, return null. * Otherwise, return OK. @@ -253,7 +274,7 @@ public T set(@NonNull String key, @NonNull String value, @NonNull SetOptions opt } /** - * Retrieve the values of multiple keys. + * Retrieves the values of multiple keys. * * @see redis.io for details. * @param keys A list of keys to retrieve values for. @@ -270,7 +291,7 @@ public T mget(@NonNull String[] keys) { } /** - * Set multiple keys to multiple values in a single operation. + * Sets multiple keys to multiple values in a single operation. * * @see redis.io for details. * @param keyValueMap A key-value map consisting of keys and their respective values to set. @@ -285,7 +306,7 @@ public T mset(@NonNull Map keyValueMap) { } /** - * Increment the number stored at key by one. If key does not exist, it + * Increments the number stored at key by one. If key does not exist, it * is set to 0 before performing the operation. * * @see redis.io for details. @@ -300,7 +321,7 @@ public T incr(@NonNull String key) { } /** - * Increment the number stored at key by amount. If key + * Increments the number stored at key by amount. If key * does not exist, it is set to 0 before performing the operation. * * @see redis.io for details. @@ -316,7 +337,7 @@ public T incrBy(@NonNull String key, long amount) { } /** - * Increment the string representing a floating point number stored at key by + * Increments the string representing a floating point number stored at key by * amount. By using a negative increment value, the result is that the value stored at * key is decremented. If key does not exist, it is set to 0 before * performing the operation. @@ -334,7 +355,7 @@ public T incrByFloat(@NonNull String key, double amount) { } /** - * Decrement the number stored at key by one. If key does not exist, it + * Decrements the number stored at key by one. If key does not exist, it * is set to 0 before performing the operation. * * @see redis.io for details. @@ -349,7 +370,7 @@ public T decr(@NonNull String key) { } /** - * Decrement the number stored at key by amount. If key + * Decrements the number stored at key by amount. If key * does not exist, it is set to 0 before performing the operation. * * @see redis.io for details. @@ -380,14 +401,13 @@ public T strlen(@NonNull String key) { } /** - * Retrieve the value associated with field in the hash stored at key. + * Retrieves the value associated with field in the hash stored at key. * * @see redis.io for details. * @param key The key of the hash. * @param field The field in the hash stored at key to retrieve from the database. * @return Command Response - The value associated with field, or null - * when field - * is not present in the hash or key does not exist. + * when field is not present in the hash or key does not exist. */ public T hget(@NonNull String key, @NonNull String field) { ArgsArray commandArgs = buildArgs(key, field); @@ -413,6 +433,26 @@ public T hset(@NonNull String key, @NonNull Map fieldValueMap) { return getThis(); } + /** + * Sets field in the hash stored at key to value, only if + * field does not yet exist.
+ * If key does not exist, a new key holding a hash is created.
+ * If field already exists, this operation has no effect. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to set the value for. + * @param value The value to set. + * @return Command Response - true if the field was set, false if the + * field already existed and was not set. + */ + public T hsetnx(@NonNull String key, @NonNull String field, @NonNull String value) { + ArgsArray commandArgs = buildArgs(key, field, value); + + protobufTransaction.addCommands(buildCommand(HSetNX, commandArgs)); + return getThis(); + } + /** * Removes the specified fields from the hash stored at key. Specified fields that do * not exist within this hash are ignored. @@ -431,6 +471,37 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns the number of fields contained in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return Command Response - The number of fields in the hash, or 0 when the key + * does not exist.
+ * If key holds a value that is not a hash, an error is returned. + */ + public T hlen(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(HLen, commandArgs)); + return getThis(); + } + + /** + * Returns all values in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return Command Response - An array of values in the hash, or an empty array + * when the key does not exist. + */ + public T hvals(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(Hvals, commandArgs)); + return getThis(); + } + /** * Returns the values associated with the specified fields in the hash stored at key. * @@ -506,7 +577,7 @@ public T hincrBy(@NonNull String key, @NonNull String field, long amount) { } /** - * Increment the string representing a floating point number stored at field in the + * Increments the string representing a floating point number stored at field in the * hash stored at key by increment. By using a negative increment value, the value * stored at field in the hash stored at key is decremented. If * field or key does not exist, it is set to 0 before performing the @@ -518,7 +589,7 @@ public T hincrBy(@NonNull String key, @NonNull String field, long amount) { * value. * @param amount The amount by which to increment or decrement the field's value. Use a negative * value to decrement. - * @returns Command Response - The value of field in the hash stored at key + * @return Command Response - The value of field in the hash stored at key * after the increment or decrement. */ public T hincrByFloat(@NonNull String key, @NonNull String field, double amount) { @@ -552,8 +623,8 @@ public T lpush(@NonNull String key, @NonNull String[] elements) { * * @see redis.io for details. * @param key The key of the list. - * @return Command Response - The value of the first element.
- * If key does not exist, null will be returned.
+ * @return Command Response - The value of the first element.
+ * If key does not exist, null will be returned. */ public T lpop(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); @@ -571,7 +642,7 @@ public T lpop(@NonNull String key) { * @param count The count of the elements to pop from the list. * @return Command Response - An array of the popped elements will be returned depending on the * list's length.
- * If key does not exist, null will be returned.
+ * If key does not exist, null will be returned. */ public T lpopCount(@NonNull String key, long count) { ArgsArray commandArgs = buildArgs(key, Long.toString(count)); @@ -582,10 +653,11 @@ public T lpopCount(@NonNull String key, long count) { /** * Returns the specified elements of the list stored at key.
- * The offsets start and end are zero-based indexes, with 0 being the - * first element of the list, 1 being the next element and so on. These offsets can also be - * negative numbers indicating offsets starting at the end of the list, with -1 being the last - * element of the list, -2 being the penultimate, and so on. + * The offsets start and end are zero-based indexes, with 0 + * being the first element of the list, 1 being the next element and so on. These + * offsets can also be negative numbers indicating offsets starting at the end of the list, with + * -1 being the last element of the list, -2 being the penultimate, and + * so on. * * @see redis.io for details. * @param key The key of the list. @@ -596,7 +668,7 @@ public T lpopCount(@NonNull String key, long count) { * end, an empty array will be returned.
* If end exceeds the actual end of the list, the range will stop at the actual * end of the list.
- * If key does not exist an empty array will be returned.
+ * If key does not exist an empty array will be returned. */ public T lrange(@NonNull String key, long start, long end) { ArgsArray commandArgs = buildArgs(key, Long.toString(start), Long.toString(end)); @@ -606,18 +678,17 @@ public T lrange(@NonNull String key, long start, long end) { } /** - * Trims an existing list so that it will contain only the specified range of elements specified. - *
- * The offsets start and end are zero-based indexes, with 0 being the - * first element of the list, 1 being the next element and so on.
+ * Trims an existing list so that it will contain only the specified range of elements specified.
+ * The offsets start and end are zero-based indexes, with 0 being the + * first element of the list,
1 being the next element and so on.
* These offsets can also be negative numbers indicating offsets starting at the end of the list, - * with -1 being the last element of the list, -2 being the penultimate, and so on. + * with -1 being the last element of the list, -2 being the penultimate, and so on. * * @see redis.io for details. * @param key The key of the list. * @param start The starting point of the range. * @param end The end of the range. - * @return Command Response - Always OK.
+ * @return Command Response - Always OK.
* If start exceeds the end of the list, or if start is greater than * end, the result will be an empty list (which causes key to be removed).
* If end exceeds the actual end of the list, it will be treated like the last @@ -637,7 +708,8 @@ public T ltrim(@NonNull String key, long start, long end) { * @see redis.io for details. * @param key The key of the list. * @return Command Response - The length of the list at key.
- * If key does not exist, it is interpreted as an empty list and 0 is returned. + * If key does not exist, it is interpreted as an empty list and 0 + * is returned. */ public T llen(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); @@ -654,14 +726,14 @@ public T llen(@NonNull String key) { * If count is negative: Removes elements equal to element moving from * tail to head.
* If count is 0 or count is greater than the occurrences of elements - * equal to element, it removes all elements equal to element.
+ * equal to element, it removes all elements equal to element. * * @see redis.io for details. * @param key The key of the list. * @param count The count of the occurrences of elements equal to element to remove. * @param element The element to remove from the list. * @return Command Response - The number of the removed elements.
- * If key does not exist, 0 is returned.
+ * If key does not exist, 0 is returned. */ public T lrem(@NonNull String key, long count, @NonNull String element) { ArgsArray commandArgs = buildArgs(key, Long.toString(count), element); @@ -695,7 +767,7 @@ public T rpush(@NonNull String key, @NonNull String[] elements) { * @see redis.io for details. * @param key The key of the list. * @return Command Response - The value of the last element.
- * If key does not exist, null will be returned.
+ * If key does not exist, null will be returned. */ public T rpop(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); @@ -712,7 +784,7 @@ public T rpop(@NonNull String key) { * @param count The count of the elements to pop from the list. * @return Command Response - An array of popped elements will be returned depending on the list's * length.
- * If key does not exist, null will be returned.
+ * If key does not exist, null will be returned. */ public T rpopCount(@NonNull String key, long count) { ArgsArray commandArgs = buildArgs(key, Long.toString(count)); @@ -722,8 +794,8 @@ public T rpopCount(@NonNull String key, long count) { } /** - * Add specified members to the set stored at key. Specified members that are already - * a member of this set are ignored. + * Adds specified members to the set stored at key. Specified members that are + * already a member of this set are ignored. * * @see redis.io for details. * @param key The key where members will be added to its set. @@ -741,7 +813,24 @@ public T sadd(@NonNull String key, @NonNull String[] members) { } /** - * Remove specified members from the set stored at key. Specified members that are + * Returns if member is a member of the set stored at key. + * + * @see redis.io for details. + * @param key The key of the set. + * @param member The member to check for existence in the set. + * @return Command Response - true if the member exists in the set, false + * otherwise. If key doesn't exist, it is treated as an empty set + * and the command returns false. + */ + public T sismember(@NonNull String key, @NonNull String member) { + ArgsArray commandArgs = buildArgs(key, member); + + protobufTransaction.addCommands(buildCommand(SIsMember, commandArgs)); + return getThis(); + } + + /** + * Removes specified members from the set stored at key. Specified members that are * not a member of this set are ignored. * * @see redis.io for details. @@ -750,7 +839,7 @@ public T sadd(@NonNull String key, @NonNull String[] members) { * @return Command Response - The number of members that were removed from the set, excluding * non-existing members. * @remarks If key does not exist, it is treated as an empty set and this command - * returns 0. + * returns 0. */ public T srem(@NonNull String key, @NonNull String[] members) { ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key)); @@ -760,7 +849,7 @@ public T srem(@NonNull String key, @NonNull String[] members) { } /** - * Retrieve all the members of the set value stored at key. + * Retrieves all the members of the set value stored at key. * * @see redis.io for details. * @param key The key from which to retrieve the set members. @@ -775,7 +864,7 @@ public T smembers(@NonNull String key) { } /** - * Retrieve the set cardinality (number of elements) of the set stored at key. + * Retrieves the set cardinality (number of elements) of the set stored at key. * * @see redis.io for details. * @param key The key from which to retrieve the number of set members. @@ -837,7 +926,7 @@ public T exists(@NonNull String[] keys) { } /** - * Unlink (delete) multiple keys from the database. A key is ignored if it does not + * Unlinks (deletes) multiple keys from the database. A key is ignored if it does not * exist. This command, similar to DEL, removes specified keys and ignores non-existent ones. * However, this command does not block the server, while DEL does. @@ -1041,7 +1130,7 @@ public T pexpireAt(@NonNull String key, long unixMilliseconds) { * @see redis.io for details. * @param key The key to set timeout on it. * @param unixMilliseconds The timeout in an absolute Unix timestamp. - * @param expireOptions The expire option. + * @param expireOptions The expiration option. * @return Command response - true if the timeout was set. false if the * timeout was not set. e.g. key doesn't exist, or operation skipped due to the * provided arguments. @@ -1073,7 +1162,7 @@ public T ttl(@NonNull String key) { } /** - * Get the current connection id. + * Gets the current connection id. * * @see redis.io for details. * @return Command response - The id of the client. @@ -1084,7 +1173,7 @@ public T clientId() { } /** - * Get the name of the current connection. + * Gets the name of the current connection. * * @see redis.io for details. * @return Command response - The name of the client connection as a string if a name is set, or @@ -1366,6 +1455,76 @@ public T zscore(@NonNull String key, @NonNull String member) { return getThis(); } + /** + * Returns the rank of member in the sorted set stored at key, with + * scores ordered from low to high.
+ * To get the rank of member with it's score, see zrankWithScore. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param member The member whose rank is to be retrieved. + * @return The rank of member in the sorted set.
+ * If key doesn't exist, or if member is not present in the set, + * null will be returned. + */ + public T zrank(@NonNull String key, @NonNull String member) { + ArgsArray commandArgs = buildArgs(new String[] {key, member}); + protobufTransaction.addCommands(buildCommand(Zrank, commandArgs)); + return getThis(); + } + + /** + * Returns the rank of member in the sorted set stored at key with it's + * score, where scores are ordered from the lowest to highest. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param member The member whose rank is to be retrieved. + * @return An array containing the rank (as Long) and score (as Double) + * of member in the sorted set.
+ * If key doesn't exist, or if member is not present in the set, + * null will be returned. + */ + public T zrankWithScore(@NonNull String key, @NonNull String member) { + ArgsArray commandArgs = buildArgs(new String[] {key, member, WITH_SCORE_REDIS_API}); + protobufTransaction.addCommands(buildCommand(Zrank, commandArgs)); + return getThis(); + } + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @return Command Response - The id of the added entry. + */ + public T xadd(@NonNull String key, @NonNull Map values) { + this.xadd(key, values, StreamAddOptions.builder().build()); + return getThis(); + } + + /** + * Adds an entry to the specified stream. + * + * @see redis.io for details. + * @param key The key of the stream. + * @param values Field-value pairs to be added to the entry. + * @param options Stream add options. + * @return Command Response - The id of the added entry, or null if {@link + * StreamAddOptions#makeStream} is set to false and no stream with the matching + * key exists. + */ + public T xadd( + @NonNull String key, @NonNull Map values, @NonNull StreamAddOptions options) { + String[] arguments = + ArrayUtils.addAll( + ArrayUtils.addFirst(options.toArgs(), key), convertMapToKeyValueStringArray(values)); + ArgsArray commandArgs = buildArgs(arguments); + protobufTransaction.addCommands(buildCommand(XAdd, commandArgs)); + return getThis(); + } + /** * Returns the remaining time to live of key that has a timeout, in milliseconds. * @@ -1425,6 +1584,232 @@ public T type(@NonNull String key) { return getThis(); } + /** + * Pops an element from the tail of the first list that is non-empty, with the given keys being + * checked in the order that they are given.
+ * Blocks the connection when there are no elements to pop from any of the given lists. + * + * @see redis.io for details. + * @apiNote BRPOP is a client blocking command, see Blocking + * Commands for more details and best practices. + * @param keys The keys of the lists to pop from. + * @param timeout The number of seconds to wait for a blocking BRPOP operation to + * complete. A value of 0 will block indefinitely. + * @return Command Response - An array containing the key from which the + * element was popped and the value of the popped element, formatted as + * [key, value]. If no element could be popped and the timeout expired, returns
+ * null
. + */ + public T brpop(@NonNull String[] keys, double timeout) { + ArgsArray commandArgs = buildArgs(ArrayUtils.add(keys, Double.toString(timeout))); + protobufTransaction.addCommands(buildCommand(Brpop, commandArgs)); + return getThis(); + } + + /** + * Inserts specified values at the head of the list, only if key already + * exists and holds a list. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the head of the list stored at key. + * @return Command Response - The length of the list after the push operation. + */ + public T lpushx(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + protobufTransaction.addCommands(buildCommand(LPushX, commandArgs)); + return getThis(); + } + + /** + * Inserts specified values at the tail of the list, only if key already + * exists and holds a list. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the tail of the list stored at key. + * @return Command Response - The length of the list after the push operation. + */ + public T rpushx(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + protobufTransaction.addCommands(buildCommand(RPushX, commandArgs)); + return getThis(); + } + + /** + * Pops an element from the head of the first list that is non-empty, with the given keys being + * checked in the order that they are given.
+ * Blocks the connection when there are no elements to pop from any of the given lists. + * + * @see redis.io for details. + * @apiNote BLPOP is a client blocking command, see Blocking + * Commands for more details and best practices. + * @param keys The keys of the lists to pop from. + * @param timeout The number of seconds to wait for a blocking BLPOP operation to + * complete. A value of 0 will block indefinitely. + * @return Command Response - An array containing the key from which the + * element was popped and the value of the popped element, formatted as + * [key, value]. If no element could be popped and the timeout expired, returns + * null. + */ + public T blpop(@NonNull String[] keys, double timeout) { + ArgsArray commandArgs = buildArgs(ArrayUtils.add(keys, Double.toString(timeout))); + protobufTransaction.addCommands(buildCommand(Blpop, commandArgs)); + return getThis(); + } + + /** + * Returns the specified range of elements in the sorted set stored at key.
+ * ZRANGE can perform different types of range queries: by index (rank), by the + * score, or by lexicographical order.
+ * To get the elements with their scores, see {@link #zrangeWithScores}. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the element with the highest + * score. + * @return Command Response - An array of elements within the specified range. If key + * does not exist, it is treated as an empty sorted set, and the command returns an empty + * array. + */ + public T zrange(@NonNull String key, @NonNull RangeQuery rangeQuery, boolean reverse) { + ArgsArray commandArgs = buildArgs(createZrangeArgs(key, rangeQuery, reverse, false)); + protobufTransaction.addCommands(buildCommand(Zrange, commandArgs)); + return getThis(); + } + + /** + * Returns the specified range of elements in the sorted set stored at key.
+ * ZRANGE can perform different types of range queries: by index (rank), by the + * score, or by lexicographical order.
+ * To get the elements with their scores, see {@link #zrangeWithScores}. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return Command Response - An array of elements within the specified range. If key + * does not exist, it is treated as an empty sorted set, and the command returns an empty + * array. + */ + public T zrange(@NonNull String key, @NonNull RangeQuery rangeQuery) { + return getThis().zrange(key, rangeQuery, false); + } + + /** + * Returns the specified range of elements with their scores in the sorted set stored at key + * . Similar to {@link #zrange} but with a WITHSCORE flag. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the element with the highest + * score. + * @return Command Response - A Map of elements and their scores within the specified + * range. If key does not exist, it is treated as an empty sorted set, and the + * command returns an empty Map. + */ + public T zrangeWithScores( + @NonNull String key, @NonNull ScoredRangeQuery rangeQuery, boolean reverse) { + ArgsArray commandArgs = buildArgs(createZrangeArgs(key, rangeQuery, reverse, true)); + protobufTransaction.addCommands(buildCommand(Zrange, commandArgs)); + return getThis(); + } + + /** + * Returns the specified range of elements with their scores in the sorted set stored at key + * . Similar to {@link #zrange} but with a WITHSCORE flag. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return Command Response - A Map of elements and their scores within the specified + * range. If key does not exist, it is treated as an empty sorted set, and the + * command returns an empty Map. + */ + public T zrangeWithScores(@NonNull String key, @NonNull ScoredRangeQuery rangeQuery) { + return getThis().zrangeWithScores(key, rangeQuery, false); + } + + /** + * Adds all elements to the HyperLogLog data structure stored at the specified key. + *
+ * Creates a new structure if the key does not exist. + * + *

When no elements are provided, and key exists and is a + * HyperLogLog, then no operation is performed. If key does not exist, then the + * HyperLogLog structure is created. + * + * @see redis.io for details. + * @param key The key of the HyperLogLog data structure to add elements into. + * @param elements An array of members to add to the HyperLogLog stored at key. + * @return Command Response - If the HyperLogLog is newly created, or if the HyperLogLog + * approximated cardinality is altered, then returns 1. Otherwise, returns + * 0. + */ + public T pfadd(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + protobufTransaction.addCommands(buildCommand(PfAdd, commandArgs)); + return getThis(); + } + + /** + * Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or + * calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily. + * + * @see redis.io for details. + * @param keys The keys of the HyperLogLog data structures to be analyzed. + * @return Command Response - The approximated cardinality of given HyperLogLog data structures. + *
+ * The cardinality of a key that does not exist is 0. + */ + public T pfcount(@NonNull String[] keys) { + ArgsArray commandArgs = buildArgs(keys); + protobufTransaction.addCommands(buildCommand(PfCount, commandArgs)); + return getThis(); + } + + /** + * Merges multiple HyperLogLog values into a unique value.
+ * If the destination variable exists, it is treated as one of the source HyperLogLog data sets, + * otherwise a new HyperLogLog is created. + * + * @see redis.io for details. + * @param destination The key of the destination HyperLogLog where the merged data sets will be + * stored. + * @param sourceKeys The keys of the HyperLogLog structures to be merged. + * @return Command Response - OK. + */ + public T pfmerge(@NonNull String destination, @NonNull String[] sourceKeys) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(sourceKeys, destination)); + protobufTransaction.addCommands(buildCommand(PfMerge, commandArgs)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/main/java/glide/api/models/ClusterValue.java b/java/client/src/main/java/glide/api/models/ClusterValue.java index 141a62a261..360b2bcaa9 100644 --- a/java/client/src/main/java/glide/api/models/ClusterValue.java +++ b/java/client/src/main/java/glide/api/models/ClusterValue.java @@ -8,7 +8,7 @@ * Represents a returned value object from a Redis server with cluster-mode enabled. The response * type may depend on the submitted {@link Route}. * - * @remark ClusterValue stores values in a union-like object. It contains a single-value or + * @remarks ClusterValue stores values in a union-like object. It contains a single-value or * multi-value response from Redis. If the command's routing is to a single node use {@link * #getSingleValue()} to return a response of type T. Otherwise, use {@link * #getMultiValue()} to return a Map of address: nodeResponse where diff --git a/java/client/src/main/java/glide/api/models/Transaction.java b/java/client/src/main/java/glide/api/models/Transaction.java index 9b015796bc..6f9aa49005 100644 --- a/java/client/src/main/java/glide/api/models/Transaction.java +++ b/java/client/src/main/java/glide/api/models/Transaction.java @@ -16,13 +16,15 @@ * command. Specific response types are documented alongside each method. * * @example - *

- *  Transaction transaction = new Transaction()
- *    .transaction.set("key", "value");
- *    .transaction.get("key");
- *  Object[] result = client.exec(transaction).get();
- *  // result contains: OK and "value"
- *  
+ *
{@code
+ * Transaction transaction = new Transaction()
+ *     .set("key", "value")
+ *     .get("key");
+ * Object[] result = client.exec(transaction).get();
+ * // result contains: OK and "value"
+ * assert result[0].equals("OK");
+ * assert result[1].equals("value");
+ * }
*/ @AllArgsConstructor public class Transaction extends BaseTransaction { @@ -32,7 +34,7 @@ protected Transaction getThis() { } /** - * Change the currently selected Redis database. + * Changes the currently selected Redis database. * * @see redis.io for details. * @param index The index of the database to select. diff --git a/java/client/src/main/java/glide/api/models/commands/RangeOptions.java b/java/client/src/main/java/glide/api/models/commands/RangeOptions.java new file mode 100644 index 0000000000..6392dcaf6c --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/RangeOptions.java @@ -0,0 +1,324 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API; +import static glide.utils.ArrayTransformUtils.concatenateArrays; + +import glide.api.commands.SortedSetBaseCommands; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Arguments for {@link SortedSetBaseCommands#zrange} and {@link + * SortedSetBaseCommands#zrangeWithScores} + * + * @see redis.io + */ +public class RangeOptions { + + /** + * Basic interface. Please use one of the following implementations: + * + *
    + *
  • {@link InfScoreBound} + *
  • {@link ScoreBoundary} + *
+ */ + public interface ScoreRange { + String toArgs(); + } + + /** Enumeration representing numeric positive and negative infinity bounds for a sorted set. */ + @RequiredArgsConstructor + public enum InfScoreBound implements ScoreRange { + POSITIVE_INFINITY("+inf"), + NEGATIVE_INFINITY("-inf"); + + private final String redisApi; + + public String toArgs() { + return redisApi; + } + } + + /** Represents a specific numeric score boundary in a sorted set. */ + public static class ScoreBoundary implements ScoreRange { + private final double bound; + private final boolean isInclusive; + + /** + * Creates a specific numeric score boundary in a sorted set. + * + * @param bound The score value. + * @param isInclusive Whether the score value is inclusive. Defaults to true if not set. + */ + public ScoreBoundary(double bound, boolean isInclusive) { + this.bound = bound; + this.isInclusive = isInclusive; + } + + /** + * Creates a specific numeric score boundary in a sorted set. + * + * @param bound The score value. + */ + public ScoreBoundary(double bound) { + this(bound, true); + } + + /** Convert the score boundary to the Redis protocol format. */ + public String toArgs() { + return (isInclusive ? "" : "(") + bound; + } + } + + /** + * Basic interface. Please use one of the following implementations: + * + *
    + *
  • {@link InfLexBound} + *
  • {@link LexBoundary} + *
+ */ + public interface LexRange { + String toArgs(); + } + + /** + * Enumeration representing lexicographic positive and negative infinity bounds for sorted set. + */ + @RequiredArgsConstructor + public enum InfLexBound implements LexRange { + POSITIVE_INFINITY("+"), + NEGATIVE_INFINITY("-"); + + private final String redisApi; + + @Override + public String toArgs() { + return redisApi; + } + } + + /** Represents a specific lexicographic boundary in a sorted set. */ + public static class LexBoundary implements LexRange { + private final String value; + private final boolean isInclusive; + + /** + * Creates a specific lexicographic boundary in a sorted set. + * + * @param value The lex value. + * @param isInclusive Whether the lex value is inclusive. Defaults to true if not set. + */ + public LexBoundary(@NonNull String value, boolean isInclusive) { + this.value = value; + this.isInclusive = isInclusive; + } + + /** + * Creates a specific lexicographic boundary in a sorted set. + * + * @param value The lex value. + */ + public LexBoundary(@NonNull String value) { + this(value, true); + } + + /** Convert the lex boundary to the Redis protocol format. */ + @Override + public String toArgs() { + return (isInclusive ? "[" : "(") + value; + } + } + + /** + * Represents a limit argument for a range query in a sorted set.
+ * The optional LIMIT argument can be used to obtain a sub-range from the matching + * elements (similar to SELECT LIMIT offset, count in SQL). + */ + @RequiredArgsConstructor + @Getter + public static class Limit { + /** The offset from the start of the range. */ + private final long offset; + + /** + * The number of elements to include in the range. A negative count returns all elements from + * the offset. + */ + private final long count; + } + + /** + * Basic interface. Please use one of the following implementations: + * + *
    + *
  • {@link RangeByIndex} + *
  • {@link RangeByScore} + *
  • {@link RangeByLex} + *
+ */ + public interface RangeQuery { + String getStart(); + + String getEnd(); + + Limit getLimit(); + } + + /** + * Represents a range by lexicographical order in a sorted set.
+ * The start and stop arguments represent lexicographical boundaries. + */ + @Getter + public static class RangeByLex implements RangeQuery { + private final String start; + private final String end; + private final Limit limit; + + /** + * Creates a range by lexicographical order in a sorted set.
+ * The start and stop arguments represent lexicographical boundaries. + * + * @param start The start lexicographic boundary. + * @param end The stop lexicographic boundary. + * @param limit The limit argument for a range query. Defaults to null. See Limit + * class for more information. + */ + public RangeByLex( + @NonNull RangeOptions.LexRange start, + @NonNull RangeOptions.LexRange end, + @NonNull Limit limit) { + this.start = start.toArgs(); + this.end = end.toArgs(); + this.limit = limit; + } + + /** + * Creates a range by lexicographical order in a sorted set.
+ * The start and stop arguments represent lexicographical boundaries. + * + * @param start The start lexicographic boundary. + * @param end The stop lexicographic boundary. + */ + public RangeByLex(@NonNull RangeOptions.LexRange start, @NonNull RangeOptions.LexRange end) { + this.start = start.toArgs(); + this.end = end.toArgs(); + this.limit = null; + } + } + + /** + * Basic interface. Please use one of the following implementations: + * + *
    + *
  • {@link RangeByIndex} + *
  • {@link RangeByScore} + *
+ */ + public interface ScoredRangeQuery extends RangeQuery {} + + /** + * Represents a range by index (rank) in a sorted set.
+ * The start and stop arguments represent zero-based indexes. + */ + @Getter + public static class RangeByIndex implements ScoredRangeQuery { + private final String start; + private final String end; + + /** + * Creates a range by index (rank) in a sorted set.
+ * The start and stop arguments represent zero-based indexes. + * + * @param start The start index of the range. + * @param end The stop index of the range. + */ + public RangeByIndex(long start, long end) { + this.start = Long.toString(start); + this.end = Long.toString(end); + } + + @Override + public Limit getLimit() { + return null; + } + } + + /** + * Represents a range by score in a sorted set.
+ * The start and stop arguments represent score boundaries. + */ + @Getter + public static class RangeByScore implements ScoredRangeQuery { + private final String start; + private final String end; + private final Limit limit; + + /** + * Creates a range by score in a sorted set.
+ * The start and stop arguments represent score boundaries. + * + * @param start The start score boundary. + * @param end The stop score boundary. + * @param limit The limit argument for a range query. Defaults to null. See Limit + * class for more information. + */ + public RangeByScore( + @NonNull RangeOptions.ScoreRange start, + @NonNull RangeOptions.ScoreRange end, + @NonNull Limit limit) { + this.start = start.toArgs(); + this.end = end.toArgs(); + this.limit = limit; + } + + /** + * Creates a range by score in a sorted set.
+ * The start and stop arguments represent score boundaries. + * + * @param start The start score boundary. + * @param end The stop score boundary. + */ + public RangeByScore( + @NonNull RangeOptions.ScoreRange start, @NonNull RangeOptions.ScoreRange end) { + this.start = start.toArgs(); + this.end = end.toArgs(); + this.limit = null; + } + } + + public static String[] createZrangeArgs( + String key, RangeQuery rangeQuery, boolean reverse, boolean withScores) { + String[] arguments = new String[] {key, rangeQuery.getStart(), rangeQuery.getEnd()}; + + if (rangeQuery instanceof RangeByScore) { + arguments = concatenateArrays(arguments, new String[] {"BYSCORE"}); + } else if (rangeQuery instanceof RangeByLex) { + arguments = concatenateArrays(arguments, new String[] {"BYLEX"}); + } + + if (reverse) { + arguments = concatenateArrays(arguments, new String[] {"REV"}); + } + + if (rangeQuery.getLimit() != null) { + arguments = + concatenateArrays( + arguments, + new String[] { + "LIMIT", + Long.toString(rangeQuery.getLimit().getOffset()), + Long.toString(rangeQuery.getLimit().getCount()) + }); + } + + if (withScores) { + arguments = concatenateArrays(arguments, new String[] {WITH_SCORES_REDIS_API}); + } + + return arguments; + } +} diff --git a/java/client/src/main/java/glide/api/models/commands/SetOptions.java b/java/client/src/main/java/glide/api/models/commands/SetOptions.java index 05f4cd5bec..ed3bfbd8ae 100644 --- a/java/client/src/main/java/glide/api/models/commands/SetOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/SetOptions.java @@ -7,7 +7,7 @@ import static glide.api.models.commands.SetOptions.ExpiryType.UNIX_MILLISECONDS; import static glide.api.models.commands.SetOptions.ExpiryType.UNIX_SECONDS; -import glide.api.commands.StringCommands; +import glide.api.commands.StringBaseCommands; import java.util.ArrayList; import java.util.List; import lombok.Builder; @@ -16,7 +16,7 @@ import redis_request.RedisRequestOuterClass.Command; /** - * Optional arguments for {@link StringCommands#set(String, String, SetOptions)} command. + * Optional arguments for {@link StringBaseCommands#set(String, String, SetOptions)} command. * * @see redis.io */ @@ -43,12 +43,12 @@ public final class SetOptions { @RequiredArgsConstructor @Getter public enum ConditionalSet { + /** Only set the key if it already exists. Equivalent to XX in the Redis API. */ + ONLY_IF_EXISTS("XX"), /** - * Only set the key if it does not already exist. Equivalent to XX in the Redis + * Only set the key if it does not already exist. Equivalent to NX in the Redis * API. */ - ONLY_IF_EXISTS("XX"), - /** Only set the key if it already exists. Equivalent to NX in the Redis API. */ ONLY_IF_DOES_NOT_EXIST("NX"); private final String redisApi; diff --git a/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java new file mode 100644 index 0000000000..1c3e1c336e --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java @@ -0,0 +1,177 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import glide.api.commands.StreamBaseCommands; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.NonNull; + +/** + * Optional arguments to {@link StreamBaseCommands#xadd} + * + * @see redis.io + */ +@Builder +public final class StreamAddOptions { + + public static final String NO_MAKE_STREAM_REDIS_API = "NOMKSTREAM"; + public static final String ID_WILDCARD_REDIS_API = "*"; + public static final String TRIM_MAXLEN_REDIS_API = "MAXLEN"; + public static final String TRIM_MINID_REDIS_API = "MINID"; + public static final String TRIM_EXACT_REDIS_API = "="; + public static final String TRIM_NOT_EXACT_REDIS_API = "~"; + public static final String TRIM_LIMIT_REDIS_API = "LIMIT"; + + /** If set, the new entry will be added with this id. */ + private final String id; + + /** + * If set to false, a new stream won't be created if no stream matches the given key. + *
+ * Equivalent to NOMKSTREAM in the Redis API. + */ + private final Boolean makeStream; + + /** If set, the add operation will also trim the older entries in the stream. */ + private final StreamTrimOptions trim; + + public abstract static class StreamTrimOptions { + /** + * If true, the stream will be trimmed exactly. Equivalent to = in the + * Redis API. Otherwise, the stream will be trimmed in a near-exact manner, which is more + * efficient, equivalent to ~ in the Redis API. + */ + protected boolean exact; + + /** If set, sets the maximal amount of entries that will be deleted. */ + protected Long limit; + + protected abstract String getMethod(); + + protected abstract String getThreshold(); + + protected List getRedisApi() { + List optionArgs = new ArrayList<>(); + + optionArgs.add(this.getMethod()); + optionArgs.add(this.exact ? TRIM_EXACT_REDIS_API : TRIM_NOT_EXACT_REDIS_API); + optionArgs.add(this.getThreshold()); + + if (this.limit != null) { + optionArgs.add(TRIM_LIMIT_REDIS_API); + optionArgs.add(this.limit.toString()); + } + + return optionArgs; + } + } + + /** Option to trim the stream according to minimum ID. */ + public static class MinId extends StreamTrimOptions { + /** Trim the stream according to entry ID. Equivalent to MINID in the Redis API. */ + private final String threshold; + + /** + * Create a trim option to trim stream based on stream ID. + * + * @param exact Whether to match exactly on the threshold. + * @param threshold Comparison id. + */ + public MinId(boolean exact, @NonNull String threshold) { + this.threshold = threshold; + this.exact = exact; + } + + /** + * Create a trim option to trim stream based on stream ID. + * + * @param exact Whether to match exactly on the threshold. + * @param threshold Comparison id. + * @param limit Max number of stream entries to be trimmed. + */ + public MinId(boolean exact, @NonNull String threshold, long limit) { + this.threshold = threshold; + this.exact = exact; + this.limit = limit; + } + + @Override + protected String getMethod() { + return TRIM_MINID_REDIS_API; + } + + @Override + protected String getThreshold() { + return threshold; + } + } + + /** Option to trim the stream according to maximum stream length. */ + public static class MaxLen extends StreamTrimOptions { + /** + * Trim the stream according to length.
+ * Equivalent to MAXLEN in the Redis API. + */ + private final Long threshold; + + /** + * Create a Max Length trim option to trim stream based on length. + * + * @param exact Whether to match exactly on the threshold. + * @param threshold Comparison count. + */ + public MaxLen(boolean exact, long threshold) { + this.threshold = threshold; + this.exact = exact; + } + + /** + * Create a Max Length trim option to trim stream entries exceeds the threshold. + * + * @param exact Whether to match exactly on the threshold. + * @param threshold Comparison count. + * @param limit Max number of stream entries to be trimmed. + */ + public MaxLen(boolean exact, long threshold, long limit) { + this.threshold = threshold; + this.exact = exact; + this.limit = limit; + } + + @Override + protected String getMethod() { + return TRIM_MAXLEN_REDIS_API; + } + + @Override + protected String getThreshold() { + return threshold.toString(); + } + } + + /** + * Converts options for Xadd into a String[]. + * + * @return String[] + */ + public String[] toArgs() { + List optionArgs = new ArrayList<>(); + + if (makeStream != null && !makeStream) { + optionArgs.add(NO_MAKE_STREAM_REDIS_API); + } + + if (trim != null) { + optionArgs.addAll(trim.getRedisApi()); + } + + if (id != null) { + optionArgs.add(id); + } else { + optionArgs.add(ID_WILDCARD_REDIS_API); + } + + return optionArgs.toArray(new String[0]); + } +} diff --git a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java index c49ee7fd91..f3dda3130b 100644 --- a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java +++ b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java @@ -47,6 +47,9 @@ public static String[] convertMapToValueKeyStringArray(Map args) { */ @SuppressWarnings("unchecked") public static U[] castArray(T[] objectArr, Class clazz) { + if (objectArr == null) { + return null; + } return Arrays.stream(objectArr) .map(clazz::cast) .toArray(size -> (U[]) Array.newInstance(clazz, size)); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index a17615d1ac..44414575ec 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -2,19 +2,31 @@ package glide.api; import static glide.api.BaseClient.OK; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_EXISTS; import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE; +import static glide.api.models.commands.StreamAddOptions.NO_MAKE_STREAM_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_EXACT_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_LIMIT_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_MAXLEN_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_MINID_REDIS_API; +import static glide.api.models.commands.StreamAddOptions.TRIM_NOT_EXACT_REDIS_API; import static glide.utils.ArrayTransformUtils.concatenateArrays; +import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static redis_request.RedisRequestOuterClass.RequestType.Blpop; +import static redis_request.RedisRequestOuterClass.RequestType.Brpop; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.ConfigGet; @@ -30,6 +42,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Expire; import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; +import static redis_request.RedisRequestOuterClass.RequestType.HLen; +import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -38,6 +52,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -45,6 +60,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; @@ -54,11 +70,16 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; +import static redis_request.RedisRequestOuterClass.RequestType.PfCount; +import static redis_request.RedisRequestOuterClass.RequestType.PfMerge; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; +import static redis_request.RedisRequestOuterClass.RequestType.RPushX; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; +import static redis_request.RedisRequestOuterClass.RequestType.SIsMember; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.Select; @@ -68,19 +89,31 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; +import static redis_request.RedisRequestOuterClass.RequestType.Zrange; +import static redis_request.RedisRequestOuterClass.RequestType.Zrank; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; +import glide.api.models.commands.RangeOptions; +import glide.api.models.commands.RangeOptions.InfLexBound; +import glide.api.models.commands.RangeOptions.InfScoreBound; +import glide.api.models.commands.RangeOptions.LexBoundary; +import glide.api.models.commands.RangeOptions.RangeByIndex; +import glide.api.models.commands.RangeOptions.RangeByLex; +import glide.api.models.commands.RangeOptions.RangeByScore; +import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SetOptions.Expiry; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.managers.CommandManager; import glide.managers.ConnectionManager; @@ -92,8 +125,12 @@ import java.util.concurrent.CompletableFuture; import lombok.SneakyThrows; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class RedisClientTest { @@ -986,6 +1023,31 @@ public void hset_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hsetnx_success() { + // setup + String key = "testKey"; + String field = "testField"; + String value = "testValue"; + String[] args = new String[] {key, field, value}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.TRUE); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HSetNX), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hsetnx(key, field, value); + Boolean payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertTrue(payload); + } + @SneakyThrows @Test public void hdel_success() { @@ -997,6 +1059,8 @@ public void hdel_success() { CompletableFuture testResponse = mock(CompletableFuture.class); when(testResponse.get()).thenReturn(value); + + // match on protobuf request when(commandManager.submitNewCommand(eq(HashDel), eq(args), any())) .thenReturn(testResponse); @@ -1009,6 +1073,53 @@ public void hdel_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hlen_success() { + // setup + String key = "testKey"; + String[] args = {key}; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HLen), eq(args), any())).thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hlen(key); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void hvals_success() { + // setup + String key = "testKey"; + String[] args = {key}; + String[] values = new String[] {"value1", "value2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(values); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Hvals), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hvals(key); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(values, payload); + } + @SneakyThrows @Test public void hmget_success() { @@ -1409,6 +1520,30 @@ public void sadd_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void sismember_returns_success() { + // setup + String key = "testKey"; + String member = "testMember"; + String[] arguments = new String[] {key, member}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(true); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(SIsMember), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.sismember(key, member); + Boolean payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertTrue(payload); + } + @SneakyThrows @Test public void srem_returns_success() { @@ -1914,6 +2049,371 @@ public void zscore_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void zrange_by_index_returns_success() { + // setup + String key = "testKey"; + RangeByIndex rangeByIndex = new RangeByIndex(0, 1); + String[] arguments = new String[] {key, rangeByIndex.getStart(), rangeByIndex.getEnd()}; + String[] value = new String[] {"one", "two"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Zrange), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrange(key, rangeByIndex); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrange_by_score_with_reverse_returns_success() { + // setup + String key = "testKey"; + RangeByScore rangeByScore = + new RangeByScore(new ScoreBoundary(3, false), InfScoreBound.NEGATIVE_INFINITY); + boolean reversed = true; + String[] arguments = + new String[] {key, rangeByScore.getStart(), rangeByScore.getEnd(), "BYSCORE", "REV"}; + String[] value = new String[] {"two", "one"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Zrange), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrange(key, rangeByScore, true); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrange_by_lex_returns_success() { + // setup + String key = "testKey"; + RangeByLex rangeByLex = + new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + String[] arguments = new String[] {key, rangeByLex.getStart(), rangeByLex.getEnd(), "BYLEX"}; + String[] value = new String[] {"a", "b"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Zrange), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrange(key, rangeByLex); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrangeWithScores_by_index_returns_success() { + // setup + String key = "testKey"; + RangeByIndex rangeByIndex = new RangeByIndex(0, 4); + String[] arguments = + new String[] {key, rangeByIndex.getStart(), rangeByIndex.getEnd(), WITH_SCORES_REDIS_API}; + Map value = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(Zrange), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.zrangeWithScores(key, rangeByIndex); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrangeWithScores_by_score_returns_success() { + // setup + String key = "testKey"; + RangeByScore rangeByScore = + new RangeByScore( + InfScoreBound.NEGATIVE_INFINITY, + InfScoreBound.POSITIVE_INFINITY, + new RangeOptions.Limit(1, 2)); + String[] arguments = + new String[] { + key, + rangeByScore.getStart(), + rangeByScore.getEnd(), + "BYSCORE", + "LIMIT", + "1", + "2", + WITH_SCORES_REDIS_API + }; + Map value = Map.of("two", 2.0, "three", 3.0); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(Zrange), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = + service.zrangeWithScores(key, rangeByScore, false); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrank_returns_success() { + // setup + String key = "testKey"; + String member = "testMember"; + String[] arguments = new String[] {key, member}; + Long value = 3L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Zrank), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrank(key, member); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrankWithScore_returns_success() { + // setup + String key = "testKey"; + String member = "testMember"; + String[] arguments = new String[] {key, member, WITH_SCORE_REDIS_API}; + Object[] value = new Object[] {1, 6.0}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Zrank), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrankWithScore(key, member); + Object[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void xadd_returns_success() { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + String[] fieldValuesArgs = convertMapToKeyValueStringArray(fieldValues); + String[] arguments = new String[] {key, "*"}; + arguments = ArrayUtils.addAll(arguments, fieldValuesArgs); + String returnId = "testId"; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + + @SneakyThrows + @Test + public void xadd_with_nomakestream_maxlen_options_returns_success() { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + StreamAddOptions options = + StreamAddOptions.builder() + .id("id") + .makeStream(false) + .trim(new StreamAddOptions.MaxLen(true, 5L)) + .build(); + + String[] arguments = + new String[] { + key, + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + "id" + }; + arguments = ArrayUtils.addAll(arguments, convertMapToKeyValueStringArray(fieldValues)); + + String returnId = "testId"; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, options); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + + private static List getStreamAddOptions() { + return List.of( + Arguments.of( + Pair.of( + // no TRIM option + StreamAddOptions.builder().id("id").makeStream(Boolean.FALSE).build(), + new String[] {"testKey", NO_MAKE_STREAM_REDIS_API, "id"}), + Pair.of( + // MAXLEN with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.MaxLen(Boolean.TRUE, 5L, 10L)) + .build(), + new String[] { + "testKey", + TRIM_MAXLEN_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MAXLEN with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.MaxLen(Boolean.FALSE, 2L)) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MAXLEN_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(2L), + "*" + }), + Pair.of( + // MIN ID with LIMIT + StreamAddOptions.builder() + .id("id") + .makeStream(Boolean.TRUE) + .trim(new StreamAddOptions.MinId(Boolean.TRUE, "testKey", 10L)) + .build(), + new String[] { + "testKey", + TRIM_MINID_REDIS_API, + TRIM_EXACT_REDIS_API, + Long.toString(5L), + TRIM_LIMIT_REDIS_API, + Long.toString(10L), + "id" + }), + Pair.of( + // MIN ID with non exact match + StreamAddOptions.builder() + .makeStream(Boolean.FALSE) + .trim(new StreamAddOptions.MinId(Boolean.FALSE, "testKey")) + .build(), + new String[] { + "testKey", + NO_MAKE_STREAM_REDIS_API, + TRIM_MINID_REDIS_API, + TRIM_NOT_EXACT_REDIS_API, + Long.toString(5L), + "*" + }))); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getStreamAddOptions") + public void xadd_with_options_returns_success(Pair optionAndArgs) { + // setup + String key = "testKey"; + Map fieldValues = new LinkedHashMap<>(); + fieldValues.put("testField1", "testValue1"); + fieldValues.put("testField2", "testValue2"); + String[] arguments = + ArrayUtils.addAll(optionAndArgs.getRight(), convertMapToKeyValueStringArray(fieldValues)); + + String returnId = "testId"; + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(returnId); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(XAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xadd(key, fieldValues, optionAndArgs.getLeft()); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(returnId, payload); + } + @SneakyThrows @Test public void type_returns_success() { @@ -1957,4 +2457,176 @@ public void time_returns_success() { assertEquals(testResponse, response); assertEquals(payload, response.get()); } + + @SneakyThrows + @Test + public void blpop_returns_success() { + // setup + String key = "key"; + double timeout = 0.5; + String[] arguments = new String[] {key, "0.5"}; + String[] value = new String[] {"key", "value"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Blpop), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.blpop(new String[] {key}, timeout); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void rpushx_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"value1", "value2"}; + String[] args = new String[] {key, "value1", "value2"}; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(RPushX), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.rpushx(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void lpushx_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"value1", "value2"}; + String[] args = new String[] {key, "value1", "value2"}; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LPushX), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lpushx(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void brpop_returns_success() { + // setup + String key = "key"; + double timeout = 0.5; + String[] arguments = new String[] {key, "0.5"}; + String[] value = new String[] {"key", "value"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Brpop), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.brpop(new String[] {key}, timeout); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pfadd_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"a", "b", "c"}; + String[] arguments = new String[] {key, "a", "b", "c"}; + Long value = 1L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PfAdd), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pfadd(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void pfcount_returns_success() { + // setup + String[] keys = new String[] {"a", "b", "c"}; + Long value = 1L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PfCount), eq(keys), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pfcount(keys); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + assertEquals(payload, response.get()); + } + + @SneakyThrows + @Test + public void pfmerge_returns_success() { + // setup + String destKey = "testKey"; + String[] sourceKeys = new String[] {"a", "b", "c"}; + String[] arguments = new String[] {destKey, "a", "b", "c"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PfMerge), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pfmerge(destKey, sourceKeys); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, response.get()); + } } diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 99c5e065d8..a4bc0eb1c9 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -1,8 +1,12 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API; import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE; import static org.junit.jupiter.api.Assertions.assertEquals; +import static redis_request.RedisRequestOuterClass.RequestType.Blpop; +import static redis_request.RedisRequestOuterClass.RequestType.Brpop; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.ConfigGet; @@ -17,6 +21,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Expire; import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; +import static redis_request.RedisRequestOuterClass.RequestType.HLen; +import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -25,6 +31,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -32,6 +39,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; @@ -41,11 +49,16 @@ import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.PTTL; import static redis_request.RedisRequestOuterClass.RequestType.Persist; +import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; +import static redis_request.RedisRequestOuterClass.RequestType.PfCount; +import static redis_request.RedisRequestOuterClass.RequestType.PfMerge; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; +import static redis_request.RedisRequestOuterClass.RequestType.RPushX; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; +import static redis_request.RedisRequestOuterClass.RequestType.SIsMember; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; @@ -54,16 +67,24 @@ import static redis_request.RedisRequestOuterClass.RequestType.Time; import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import static redis_request.RedisRequestOuterClass.RequestType.XAdd; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; +import static redis_request.RedisRequestOuterClass.RequestType.Zrange; +import static redis_request.RedisRequestOuterClass.RequestType.Zrank; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; +import glide.api.models.commands.RangeOptions.InfScoreBound; +import glide.api.models.commands.RangeOptions.Limit; +import glide.api.models.commands.RangeOptions.RangeByScore; +import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -155,12 +176,24 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) HashSet, ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build())); + transaction.hsetnx("key", "field", "value"); + results.add( + Pair.of( + HSetNX, + ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build())); + transaction.hget("key", "field"); results.add(Pair.of(HashGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); transaction.hdel("key", new String[] {"field"}); results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hlen("key"); + results.add(Pair.of(HLen, ArgsArray.newBuilder().addArgs("key").build())); + + transaction.hvals("key"); + results.add(Pair.of(Hvals, ArgsArray.newBuilder().addArgs("key").build())); + transaction.hmget("key", new String[] {"field"}); results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); @@ -223,6 +256,10 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); + transaction.sismember("key", "member"); + results.add( + Pair.of(SIsMember, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + transaction.srem("key", new String[] {"value"}); results.add(Pair.of(SRem, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); @@ -405,6 +442,41 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zscore("key", "member"); results.add(Pair.of(ZScore, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + transaction.zrank("key", "member"); + results.add(Pair.of(Zrank, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + + transaction.zrankWithScore("key", "member"); + results.add( + Pair.of( + Zrank, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("member") + .addArgs(WITH_SCORE_REDIS_API) + .build())); + + transaction.xadd("key", Map.of("field1", "foo1")); + results.add( + Pair.of( + XAdd, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("*") + .addArgs("field1") + .addArgs("foo1") + .build())); + + transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build()); + results.add( + Pair.of( + XAdd, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("id") + .addArgs("field1") + .addArgs("foo1") + .build())); + transaction.time(); results.add(Pair.of(Time, ArgsArray.newBuilder().build())); @@ -414,6 +486,79 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.type("key"); results.add(Pair.of(Type, ArgsArray.newBuilder().addArgs("key").build())); + transaction.brpop(new String[] {"key1", "key2"}, 0.5); + results.add( + Pair.of( + Brpop, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").addArgs("0.5").build())); + transaction.blpop(new String[] {"key1", "key2"}, 0.5); + results.add( + Pair.of( + Blpop, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").addArgs("0.5").build())); + + transaction.rpushx("key", new String[] {"element1", "element2"}); + results.add( + Pair.of( + RPushX, + ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + + transaction.lpushx("key", new String[] {"element1", "element2"}); + results.add( + Pair.of( + LPushX, + ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + + transaction.zrange( + "key", + new RangeByScore( + InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), + true); + results.add( + Pair.of( + Zrange, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("-inf") + .addArgs("(3.0") + .addArgs("BYSCORE") + .addArgs("REV") + .addArgs("LIMIT") + .addArgs("1") + .addArgs("2") + .build())); + + transaction.zrangeWithScores( + "key", + new RangeByScore( + new ScoreBoundary(5, true), InfScoreBound.POSITIVE_INFINITY, new Limit(1, 2)), + false); + results.add( + Pair.of( + Zrange, + ArgsArray.newBuilder() + .addArgs("key") + .addArgs("5.0") + .addArgs("+inf") + .addArgs("BYSCORE") + .addArgs("LIMIT") + .addArgs("1") + .addArgs("2") + .addArgs(WITH_SCORES_REDIS_API) + .build())); + + transaction.pfadd("hll", new String[] {"a", "b", "c"}); + results.add( + Pair.of( + PfAdd, + ArgsArray.newBuilder().addArgs("hll").addArgs("a").addArgs("b").addArgs("c").build())); + + transaction.pfcount(new String[] {"hll1", "hll2"}); + results.add(Pair.of(PfCount, ArgsArray.newBuilder().addArgs("hll1").addArgs("hll2").build())); + transaction.pfmerge("hll", new String[] {"hll1", "hll2"}); + results.add( + Pair.of( + PfMerge, + ArgsArray.newBuilder().addArgs("hll").addArgs("hll1").addArgs("hll2").build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/ConnectionTests.java b/java/integTest/src/test/java/glide/ConnectionTests.java index 96cc52008b..254ffad838 100644 --- a/java/integTest/src/test/java/glide/ConnectionTests.java +++ b/java/integTest/src/test/java/glide/ConnectionTests.java @@ -4,10 +4,11 @@ import glide.api.RedisClient; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; -import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +@Timeout(10) // seconds public class ConnectionTests { @Test @@ -19,7 +20,7 @@ public void basic_client() { .address( NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) .build()) - .get(10, TimeUnit.SECONDS); + .get(); regularClient.close(); } @@ -31,7 +32,7 @@ public void cluster_client() { RedisClientConfiguration.builder() .address(NodeAddress.builder().port(TestConfiguration.CLUSTER_PORTS[0]).build()) .build()) - .get(10, TimeUnit.SECONDS); + .get(); regularClient.close(); } } diff --git a/java/integTest/src/test/java/glide/ErrorHandlingTests.java b/java/integTest/src/test/java/glide/ErrorHandlingTests.java index c4ae487b6c..2776de3565 100644 --- a/java/integTest/src/test/java/glide/ErrorHandlingTests.java +++ b/java/integTest/src/test/java/glide/ErrorHandlingTests.java @@ -12,10 +12,11 @@ import glide.api.models.exceptions.RequestException; import java.net.ServerSocket; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +@Timeout(10) // seconds public class ErrorHandlingTests { @Test @@ -29,7 +30,7 @@ public void basic_client_tries_to_connect_to_wrong_address() { RedisClientConfiguration.builder() .address(NodeAddress.builder().port(getFreePort()).build()) .build()) - .get(10, TimeUnit.SECONDS)); + .get()); assertAll( () -> assertTrue(exception.getCause() instanceof ClosingException), () -> assertTrue(exception.getCause().getMessage().contains("Connection refused"))); @@ -44,11 +45,11 @@ public void basic_client_tries_wrong_command() { .address( NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) .build()) - .get(10, TimeUnit.SECONDS)) { + .get()) { var exception = assertThrows( ExecutionException.class, - () -> regularClient.customCommand(new String[] {"pewpew"}).get(10, TimeUnit.SECONDS)); + () -> regularClient.customCommand(new String[] {"pewpew"}).get()); assertAll( () -> assertTrue(exception.getCause() instanceof RequestException), () -> assertTrue(exception.getCause().getMessage().contains("unknown command"))); @@ -64,14 +65,11 @@ public void basic_client_tries_wrong_command_arguments() { .address( NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) .build()) - .get(10, TimeUnit.SECONDS)) { + .get()) { var exception = assertThrows( ExecutionException.class, - () -> - regularClient - .customCommand(new String[] {"ping", "pang", "pong"}) - .get(10, TimeUnit.SECONDS)); + () -> regularClient.customCommand(new String[] {"ping", "pang", "pong"}).get()); assertAll( () -> assertTrue(exception.getCause() instanceof RequestException), () -> diff --git a/java/integTest/src/test/java/glide/SharedClientTests.java b/java/integTest/src/test/java/glide/SharedClientTests.java index 595e7c9547..fa343db818 100644 --- a/java/integTest/src/test/java/glide/SharedClientTests.java +++ b/java/integTest/src/test/java/glide/SharedClientTests.java @@ -25,7 +25,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -@Timeout(25) +@Timeout(25) // seconds public class SharedClientTests { private static RedisClient standaloneClient = null; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a7ddbe0dc9..cb902e7de1 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -21,14 +21,24 @@ import glide.api.RedisClusterClient; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; +import glide.api.models.commands.RangeOptions.InfLexBound; +import glide.api.models.commands.RangeOptions.InfScoreBound; +import glide.api.models.commands.RangeOptions.LexBoundary; +import glide.api.models.commands.RangeOptions.Limit; +import glide.api.models.commands.RangeOptions.RangeByIndex; +import glide.api.models.commands.RangeOptions.RangeByLex; +import glide.api.models.commands.RangeOptions.RangeByScore; +import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.ScriptOptions; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import glide.api.models.commands.ZaddOptions; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.api.models.exceptions.RequestException; import java.time.Instant; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -44,7 +54,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -@Timeout(10) +@Timeout(10) // seconds public class SharedCommandTests { private static RedisClient standaloneClient = null; @@ -63,6 +73,7 @@ public static void init() { RedisClient.CreateClient( RedisClientConfiguration.builder() .address(NodeAddress.builder().port(STANDALONE_PORTS[0]).build()) + .requestTimeout(5000) .build()) .get(); @@ -446,6 +457,25 @@ public void hset_hget_existing_fields_non_existing_fields(BaseClient client) { assertNull(client.hget(key, "non_existing_field").get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hsetnx(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + + assertTrue(client.hsetnx(key1, field, "value").get()); + assertFalse(client.hsetnx(key1, field, "newValue").get()); + assertEquals("value", client.hget(key1, field).get()); + + // Key exists, but it is not a hash + assertEquals(OK, client.set(key2, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hsetnx(key2, field, "value").get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") @@ -463,6 +493,56 @@ public void hdel_multiple_existing_fields_non_existing_field_non_existing_key(Ba assertEquals(0, client.hdel("non_existing_key", new String[] {field3}).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hlen(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, value, field2, value); + + assertEquals(2, client.hset(key1, fieldValueMap).get()); + assertEquals(2, client.hlen(key1).get()); + assertEquals(1, client.hdel(key1, new String[] {field1}).get()); + assertEquals(1, client.hlen(key1).get()); + assertEquals(0, client.hlen("nonExistingHash").get()); + + // Key exists, but it is not a hash + assertEquals(OK, client.set(key2, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hlen(key2).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hvals(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, "value1", field2, "value2"); + + assertEquals(2, client.hset(key1, fieldValueMap).get()); + + String[] hvalsPayload = client.hvals(key1).get(); + Arrays.sort(hvalsPayload); // ordering for values by hvals is not guaranteed + assertArrayEquals(new String[] {"value1", "value2"}, hvalsPayload); + + assertEquals(1, client.hdel(key1, new String[] {field1}).get()); + assertArrayEquals(new String[] {"value2"}, client.hvals(key1).get()); + assertArrayEquals(new String[] {}, client.hvals("nonExistingKey").get()); + + assertEquals(OK, client.set(key2, "value2").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hvals(key2).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") @@ -744,6 +824,25 @@ public void sadd_srem_scard_smembers_key_with_non_set_value(BaseClient client) { assertTrue(e.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void sismember(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String member = UUID.randomUUID().toString(); + + assertEquals(1, client.sadd(key1, new String[] {member}).get()); + assertTrue(client.sismember(key1, member).get()); + assertFalse(client.sismember(key1, "nonExistingMember").get()); + assertFalse(client.sismember("nonExistingKey", member).get()); + + assertEquals(OK, client.set(key2, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.sismember(key2, member).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") @@ -1171,6 +1270,121 @@ public void zscore(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zrank(BaseClient client) { + String key = UUID.randomUUID().toString(); + Map membersScores = Map.of("one", 1.5, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(key, membersScores).get()); + assertEquals(0, client.zrank(key, "one").get()); + + if (REDIS_VERSION.isGreaterThanOrEqualTo("7.2.0")) { + assertArrayEquals(new Object[] {0L, 1.5}, client.zrankWithScore(key, "one").get()); + assertNull(client.zrankWithScore(key, "nonExistingMember").get()); + assertNull(client.zrankWithScore("nonExistingKey", "nonExistingMember").get()); + } + assertNull(client.zrank(key, "nonExistingMember").get()); + assertNull(client.zrank("nonExistingKey", "nonExistingMember").get()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.zrank(key, "one").get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void xadd(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + + assertNull( + client + .xadd( + key, + Map.of(field1, "foo0", field2, "bar0"), + StreamAddOptions.builder().makeStream(Boolean.FALSE).build()) + .get()); + + String timestamp1 = "0-1"; + assertEquals( + timestamp1, + client + .xadd( + key, + Map.of(field1, "foo1", field2, "bar1"), + StreamAddOptions.builder().id(timestamp1).build()) + .get()); + + assertNotNull(client.xadd(key, Map.of(field1, "foo2", field2, "bar2")).get()); + // TODO update test when XLEN is available + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + // this will trim the first entry. + String id = + client + .xadd( + key, + Map.of(field1, "foo3", field2, "bar3"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.MaxLen(Boolean.TRUE, 2L)) + .build()) + .get(); + assertNotNull(id); + // TODO update test when XLEN is available + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + // this will trim the second entry. + assertNotNull( + client + .xadd( + key, + Map.of(field1, "foo4", field2, "bar4"), + StreamAddOptions.builder() + .trim(new StreamAddOptions.MinId(Boolean.TRUE, id)) + .build()) + .get()); + // TODO update test when XLEN is available + if (client instanceof RedisClient) { + assertEquals(2L, ((RedisClient) client).customCommand(new String[] {"XLEN", key}).get()); + } else if (client instanceof RedisClusterClient) { + assertEquals( + 2L, + ((RedisClusterClient) client) + .customCommand(new String[] {"XLEN", key}) + .get() + .getSingleValue()); + } + + /** + * TODO add test to XTRIM on maxlen expect( await client.xtrim(key, { method: "maxlen", + * threshold: 1, exact: true, }), ).toEqual(1); expect(await client.customCommand(["XLEN", + * key])).toEqual(1); + */ + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") @@ -1187,16 +1401,8 @@ public void type(BaseClient client) { assertEquals(1, client.lpush(listKey, new String[] {"value"}).get()); assertEquals(1, client.hset(hashKey, Map.of("1", "2")).get()); assertEquals(1, client.sadd(setKey, new String[] {"value"}).get()); - assertEquals(1, client.zadd(zsetKey, Map.of("1", 2.)).get()); - - // TODO: update after adding XADD - // use custom command until XADD is implemented - String[] args = new String[] {"XADD", streamKey, "*", "field", "value"}; - if (client instanceof RedisClient) { - assertNotNull(((RedisClient) client).customCommand(args).get()); - } else if (client instanceof RedisClusterClient) { - assertNotNull(((RedisClusterClient) client).customCommand(args).get().getSingleValue()); - } + assertEquals(1, client.zadd(zsetKey, Map.of("1", 2d)).get()); + assertNotNull(client.xadd(streamKey, Map.of("field", "value"))); assertTrue("none".equalsIgnoreCase(client.type(nonExistingKey).get())); assertTrue("string".equalsIgnoreCase(client.type(stringKey).get())); @@ -1206,4 +1412,306 @@ public void type(BaseClient client) { assertTrue("zset".equalsIgnoreCase(client.type(zsetKey).get())); assertTrue("stream".equalsIgnoreCase(client.type(streamKey).get())); } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void brpop(BaseClient client) { + String listKey1 = "{listKey}-1-" + UUID.randomUUID(); + String listKey2 = "{listKey}-2-" + UUID.randomUUID(); + String value1 = "value1-" + UUID.randomUUID(); + String value2 = "value2-" + UUID.randomUUID(); + assertEquals(2, client.lpush(listKey1, new String[] {value1, value2}).get()); + + var response = client.brpop(new String[] {listKey1, listKey2}, 0.5).get(); + assertArrayEquals(new String[] {listKey1, value1}, response); + + // nothing popped out + assertNull( + client + .brpop(new String[] {listKey2}, REDIS_VERSION.isLowerThan("7.0.0") ? 1. : 0.001) + .get()); + + // Key exists, but it is not a list + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.brpop(new String[] {"foo"}, .0001).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void rpushx(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String key3 = UUID.randomUUID().toString(); + + assertEquals(1, client.rpush(key1, new String[] {"0"}).get()); + assertEquals(4, client.rpushx(key1, new String[] {"1", "2", "3"}).get()); + assertArrayEquals(new String[] {"0", "1", "2", "3"}, client.lrange(key1, 0, -1).get()); + + assertEquals(0, client.rpushx(key2, new String[] {"1"}).get()); + assertArrayEquals(new String[0], client.lrange(key2, 0, -1).get()); + + // Key exists, but it is not a list + assertEquals(OK, client.set(key3, "bar").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.rpushx(key3, new String[] {"_"}).get()); + assertTrue(executionException.getCause() instanceof RequestException); + // empty element list + executionException = + assertThrows(ExecutionException.class, () -> client.rpushx(key2, new String[0]).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void blpop(BaseClient client) { + String listKey1 = "{listKey}-1-" + UUID.randomUUID(); + String listKey2 = "{listKey}-2-" + UUID.randomUUID(); + String value1 = "value1-" + UUID.randomUUID(); + String value2 = "value2-" + UUID.randomUUID(); + assertEquals(2, client.lpush(listKey1, new String[] {value1, value2}).get()); + + var response = client.blpop(new String[] {listKey1, listKey2}, 0.5).get(); + assertArrayEquals(new String[] {listKey1, value2}, response); + + // nothing popped out + assertNull( + client + .blpop(new String[] {listKey2}, REDIS_VERSION.isLowerThan("7.0.0") ? 1. : 0.001) + .get()); + + // Key exists, but it is not a list + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.blpop(new String[] {"foo"}, .0001).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void lpushx(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String key3 = UUID.randomUUID().toString(); + + assertEquals(1, client.lpush(key1, new String[] {"0"}).get()); + assertEquals(4, client.lpushx(key1, new String[] {"1", "2", "3"}).get()); + assertArrayEquals(new String[] {"3", "2", "1", "0"}, client.lrange(key1, 0, -1).get()); + + assertEquals(0, client.lpushx(key2, new String[] {"1"}).get()); + assertArrayEquals(new String[0], client.lrange(key2, 0, -1).get()); + + // Key exists, but it is not a list + assertEquals(OK, client.set(key3, "bar").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.lpushx(key3, new String[] {"_"}).get()); + // empty element list + executionException = + assertThrows(ExecutionException.class, () -> client.lpushx(key2, new String[0]).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zrange_by_index(BaseClient client) { + String key = UUID.randomUUID().toString(); + Map membersScores = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(key, membersScores).get()); + + RangeByIndex query = new RangeByIndex(0, 1); + assertArrayEquals(new String[] {"one", "two"}, client.zrange(key, query).get()); + + query = new RangeByIndex(0, -1); + assertEquals( + Map.of("one", 1.0, "two", 2.0, "three", 3.0), client.zrangeWithScores(key, query).get()); + + query = new RangeByIndex(0, 1); + assertArrayEquals(new String[] {"three", "two"}, client.zrange(key, query, true).get()); + + query = new RangeByIndex(3, 1); + assertArrayEquals(new String[] {}, client.zrange(key, query, true).get()); + assertTrue(client.zrangeWithScores(key, query).get().isEmpty()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zrange_by_score(BaseClient client) { + String key = UUID.randomUUID().toString(); + Map membersScores = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(key, membersScores).get()); + + RangeByScore query = + new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false)); + assertArrayEquals(new String[] {"one", "two"}, client.zrange(key, query).get()); + + query = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, InfScoreBound.POSITIVE_INFINITY); + assertEquals( + Map.of("one", 1.0, "two", 2.0, "three", 3.0), client.zrangeWithScores(key, query).get()); + + query = new RangeByScore(new ScoreBoundary(3, false), InfScoreBound.NEGATIVE_INFINITY); + assertArrayEquals(new String[] {"two", "one"}, client.zrange(key, query, true).get()); + + query = + new RangeByScore( + InfScoreBound.NEGATIVE_INFINITY, InfScoreBound.POSITIVE_INFINITY, new Limit(1, 2)); + assertArrayEquals(new String[] {"two", "three"}, client.zrange(key, query).get()); + + query = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false)); + assertArrayEquals( + new String[] {}, + client + .zrange(key, query, true) + .get()); // stop is greater than start with reverse set to True + + query = new RangeByScore(InfScoreBound.POSITIVE_INFINITY, new ScoreBoundary(3, false)); + assertArrayEquals( + new String[] {}, client.zrange(key, query, true).get()); // start is greater than stop + + query = new RangeByScore(InfScoreBound.POSITIVE_INFINITY, new ScoreBoundary(3, false)); + assertTrue(client.zrangeWithScores(key, query).get().isEmpty()); // start is greater than stop + + query = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false)); + assertTrue( + client + .zrangeWithScores(key, query, true) + .get() + .isEmpty()); // stop is greater than start with reverse set to True + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zrange_by_lex(BaseClient client) { + String key = UUID.randomUUID().toString(); + Map membersScores = Map.of("a", 1.0, "b", 2.0, "c", 3.0); + assertEquals(3, client.zadd(key, membersScores).get()); + + RangeByLex query = new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + assertArrayEquals(new String[] {"a", "b"}, client.zrange(key, query).get()); + + query = + new RangeByLex( + InfLexBound.NEGATIVE_INFINITY, InfLexBound.POSITIVE_INFINITY, new Limit(1, 2)); + assertArrayEquals(new String[] {"b", "c"}, client.zrange(key, query).get()); + + query = new RangeByLex(new LexBoundary("c", false), InfLexBound.NEGATIVE_INFINITY); + assertArrayEquals(new String[] {"b", "a"}, client.zrange(key, query, true).get()); + + query = new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + assertArrayEquals( + new String[] {}, + client + .zrange(key, query, true) + .get()); // stop is greater than start with reverse set to True + + query = new RangeByLex(InfLexBound.POSITIVE_INFINITY, new LexBoundary("c", false)); + assertArrayEquals( + new String[] {}, client.zrange(key, query).get()); // start is greater than stop + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zrange_with_different_types_of_keys(BaseClient client) { + String key = UUID.randomUUID().toString(); + RangeByIndex query = new RangeByIndex(0, 1); + + assertArrayEquals(new String[] {}, client.zrange("non_existing_key", query).get()); + + assertTrue( + client + .zrangeWithScores("non_existing_key", query) + .get() + .isEmpty()); // start is greater than stop + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.zrange(key, query).get()); + assertTrue(executionException.getCause() instanceof RequestException); + + executionException = + assertThrows(ExecutionException.class, () -> client.zrangeWithScores(key, query).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void pfadd(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(1, client.pfadd(key, new String[0]).get()); + assertEquals(1, client.pfadd(key, new String[] {"one", "two"}).get()); + assertEquals(0, client.pfadd(key, new String[] {"two"}).get()); + assertEquals(0, client.pfadd(key, new String[0]).get()); + + // Key exists, but it is not a HyperLogLog + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.pfadd("foo", new String[0]).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void pfcount(BaseClient client) { + String key1 = "{test}-hll1-" + UUID.randomUUID(); + String key2 = "{test}-hll2-" + UUID.randomUUID(); + String key3 = "{test}-hll3-" + UUID.randomUUID(); + assertEquals(1, client.pfadd(key1, new String[] {"a", "b", "c"}).get()); + assertEquals(1, client.pfadd(key2, new String[] {"b", "c", "d"}).get()); + assertEquals(3, client.pfcount(new String[] {key1}).get()); + assertEquals(3, client.pfcount(new String[] {key2}).get()); + assertEquals(4, client.pfcount(new String[] {key1, key2}).get()); + assertEquals(4, client.pfcount(new String[] {key1, key2, key3}).get()); + // empty HyperLogLog data set + assertEquals(1, client.pfadd(key3, new String[0]).get()); + assertEquals(0, client.pfcount(new String[] {key3}).get()); + + // Key exists, but it is not a HyperLogLog + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.pfcount(new String[] {"foo"}).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void pfmerge(BaseClient client) { + String key1 = "{test}-hll1-" + UUID.randomUUID(); + String key2 = "{test}-hll2-" + UUID.randomUUID(); + String key3 = "{test}-hll3-" + UUID.randomUUID(); + assertEquals(1, client.pfadd(key1, new String[] {"a", "b", "c"}).get()); + assertEquals(1, client.pfadd(key2, new String[] {"b", "c", "d"}).get()); + // new HyperLogLog data set + assertEquals(OK, client.pfmerge(key3, new String[] {key1, key2}).get()); + assertEquals( + client.pfcount(new String[] {key1, key2}).get(), client.pfcount(new String[] {key3}).get()); + // existing HyperLogLog data set + assertEquals(OK, client.pfmerge(key1, new String[] {key2}).get()); + assertEquals( + client.pfcount(new String[] {key1, key2}).get(), client.pfcount(new String[] {key1}).get()); + + // Key exists, but it is not a HyperLogLog + assertEquals(OK, client.set("foo", "bar").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.pfmerge("foo", new String[] {key1}).get()); + assertTrue(executionException.getCause() instanceof RequestException); + executionException = + assertThrows( + ExecutionException.class, () -> client.pfmerge(key1, new String[] {"foo"}).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 5af67f20b5..f8b8002cfa 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -4,7 +4,9 @@ import static glide.api.BaseClient.OK; import glide.api.models.BaseTransaction; +import glide.api.models.commands.RangeOptions.RangeByIndex; import glide.api.models.commands.SetOptions; +import glide.api.models.commands.StreamAddOptions; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -16,8 +18,13 @@ public class TransactionTestUtilities { private static final String key4 = "{key}" + UUID.randomUUID(); private static final String key5 = "{key}" + UUID.randomUUID(); private static final String key6 = "{key}" + UUID.randomUUID(); + private static final String listKey3 = "{key}:listKey3-" + UUID.randomUUID(); private static final String key7 = "{key}" + UUID.randomUUID(); private static final String key8 = "{key}" + UUID.randomUUID(); + private static final String key9 = "{key}" + UUID.randomUUID(); + private static final String hllKey1 = "{key}:hllKey1-" + UUID.randomUUID(); + private static final String hllKey2 = "{key}:hllKey2-" + UUID.randomUUID(); + private static final String hllKey3 = "{key}:hllKey3-" + UUID.randomUUID(); private static final String value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); private static final String value3 = UUID.randomUUID().toString(); @@ -59,10 +66,13 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); + baseTransaction.hlen(key4); baseTransaction.hexists(key4, field2); + baseTransaction.hsetnx(key4, field1, value1); baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hgetall(key4); baseTransaction.hdel(key4, new String[] {field1}); + baseTransaction.hvals(key4); baseTransaction.hincrBy(key4, field3, 5); baseTransaction.hincrByFloat(key4, field3, 5.5); @@ -82,16 +92,27 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.sadd(key7, new String[] {"baz", "foo"}); baseTransaction.srem(key7, new String[] {"foo"}); baseTransaction.scard(key7); + baseTransaction.sismember(key7, "baz"); baseTransaction.smembers(key7); baseTransaction.zadd(key8, Map.of("one", 1.0, "two", 2.0, "three", 3.0)); + baseTransaction.zrank(key8, "one"); baseTransaction.zaddIncr(key8, "one", 3); baseTransaction.zrem(key8, new String[] {"one"}); baseTransaction.zcard(key8); + baseTransaction.zrange(key8, new RangeByIndex(0, 1)); + baseTransaction.zrangeWithScores(key8, new RangeByIndex(0, 1)); baseTransaction.zscore(key8, "two"); baseTransaction.zpopmin(key8); baseTransaction.zpopmax(key8); + baseTransaction.xadd( + key9, Map.of("field1", "value1"), StreamAddOptions.builder().id("0-1").build()); + baseTransaction.xadd( + key9, Map.of("field2", "value2"), StreamAddOptions.builder().id("0-2").build()); + baseTransaction.xadd( + key9, Map.of("field3", "value3"), StreamAddOptions.builder().id("0-3").build()); + baseTransaction.configSet(Map.of("timeout", "1000")); baseTransaction.configGet(new String[] {"timeout"}); @@ -99,6 +120,20 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.echo("GLIDE"); + // TODO should be before LINDEX from #1219 and BRPOP/BLPOP from #1218 + baseTransaction.rpushx(listKey3, new String[] {"_"}).lpushx(listKey3, new String[] {"_"}); + + baseTransaction + .lpush(listKey3, new String[] {value1, value2, value3}) + .blpop(new String[] {listKey3}, 0.01) + .brpop(new String[] {listKey3}, 0.01); + + baseTransaction.pfadd(hllKey1, new String[] {"a", "b", "c"}); + baseTransaction.pfcount(new String[] {hllKey1, hllKey2}); + baseTransaction + .pfmerge(hllKey3, new String[] {hllKey1, hllKey2}) + .pfcount(new String[] {hllKey3}); + return baseTransaction; } @@ -126,10 +161,13 @@ public static Object[] transactionTestResult() { 1L, 2L, value1, + 2L, // hlen(key4) true, + Boolean.FALSE, // hsetnx(key4, field1, value1) new String[] {value1, null, value2}, Map.of(field1, value1, field2, value2), 1L, + new String[] {value2}, // hvals(key4) 5L, 10.5, 5L, @@ -145,18 +183,37 @@ public static Object[] transactionTestResult() { 2L, 1L, 1L, + true, // sismember(key7, "baz") Set.of("baz"), 3L, + 0L, // zrank(key8, "one") 4.0, 1L, 2L, + new String[] {"two", "three"}, // zrange + Map.of("two", 2.0, "three", 3.0), // zrangeWithScores 2.0, // zscore(key8, "two") Map.of("two", 2.0), // zpopmin(key8) Map.of("three", 3.0), // zpopmax(key8) + "0-1", // xadd(key9, Map.of("field1", "value1"), + // StreamAddOptions.builder().id("0-1").build()); + "0-2", // xadd(key9, Map.of("field2", "value2"), + // StreamAddOptions.builder().id("0-2").build()); + "0-3", // xadd(key9, Map.of("field3", "value3"), + // StreamAddOptions.builder().id("0-3").build()); OK, Map.of("timeout", "1000"), OK, "GLIDE", // echo + 0L, // rpushx(listKey3, new String[] { "_" }) + 0L, // lpushx(listKey3, new String[] { "_" }) + 3L, // lpush(listKey3, new String[] { value1, value2, value3}) + new String[] {listKey3, value3}, // blpop(new String[] { listKey3 }, 0.01) + new String[] {listKey3, value1}, // brpop(new String[] { listKey3 }, 0.01); + 1L, // pfadd(hllKey1, new String[] {"a", "b", "c"}) + 3L, // pfcount(new String[] { hllKey1, hllKey2 });; + OK, // pfmerge(hllKey3, new String[] {hllKey1, hllKey2}) + 3L, // pfcount(new String[] { hllKey3 }) }; } } diff --git a/java/integTest/src/test/java/glide/cluster/ClusterClientTests.java b/java/integTest/src/test/java/glide/cluster/ClusterClientTests.java index aefeb36ad3..c3eb503eaf 100644 --- a/java/integTest/src/test/java/glide/cluster/ClusterClientTests.java +++ b/java/integTest/src/test/java/glide/cluster/ClusterClientTests.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -@Timeout(10) +@Timeout(10) // seconds public class ClusterClientTests { @SneakyThrows diff --git a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java index 6028d39178..f649e08856 100644 --- a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java +++ b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java @@ -17,12 +17,13 @@ import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClusterClientConfiguration; import java.util.Arrays; -import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +@Timeout(10) // seconds public class ClusterTransactionTests { private static RedisClusterClient clusterClient = null; @@ -36,7 +37,7 @@ public static void init() { .address(NodeAddress.builder().port(TestConfiguration.CLUSTER_PORTS[0]).build()) .requestTimeout(5000) .build()) - .get(10, TimeUnit.SECONDS); + .get(); } @AfterAll @@ -49,7 +50,7 @@ public static void teardown() { @SneakyThrows public void custom_command_info() { ClusterTransaction transaction = new ClusterTransaction().customCommand(new String[] {"info"}); - Object[] result = clusterClient.exec(transaction).get(10, TimeUnit.SECONDS); + Object[] result = clusterClient.exec(transaction).get(); assertTrue(((String) result[0]).contains("# Stats")); } @@ -68,8 +69,7 @@ public void WATCH_transaction_failure_returns_null() { @SneakyThrows public void info_simple_route_test() { ClusterTransaction transaction = new ClusterTransaction().info().info(); - ClusterValue[] result = - clusterClient.exec(transaction, RANDOM).get(10, TimeUnit.SECONDS); + ClusterValue[] result = clusterClient.exec(transaction, RANDOM).get(); // check single-value result assertTrue(result[0].hasSingleData()); @@ -85,8 +85,7 @@ public void test_cluster_transactions() { ClusterTransaction transaction = (ClusterTransaction) transactionTest(new ClusterTransaction()); Object[] expectedResult = transactionTestResult(); - ClusterValue[] clusterValues = - clusterClient.exec(transaction, RANDOM).get(10, TimeUnit.SECONDS); + ClusterValue[] clusterValues = clusterClient.exec(transaction, RANDOM).get(); Object[] results = Arrays.stream(clusterValues) .map(v -> v.hasSingleData() ? v.getSingleValue() : v.getMultiValue()) diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 1607224db5..8c3cfe5908 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -40,14 +40,13 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -@Timeout(10) +@Timeout(10) // seconds public class CommandTests { private static RedisClusterClient clusterClient = null; @@ -129,8 +128,7 @@ public void custom_command_info() { @Test @SneakyThrows public void custom_command_ping() { - ClusterValue data = - clusterClient.customCommand(new String[] {"ping"}).get(10, TimeUnit.SECONDS); + ClusterValue data = clusterClient.customCommand(new String[] {"ping"}).get(); assertEquals("PONG", data.getSingleValue()); } diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 85a6187274..26c7adccb2 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -@Timeout(10) +@Timeout(10) // seconds public class CommandTests { private static final String INITIAL_VALUE = "VALUE"; diff --git a/java/integTest/src/test/java/glide/standalone/StandaloneClientTests.java b/java/integTest/src/test/java/glide/standalone/StandaloneClientTests.java index 4356f1e333..3f36952049 100644 --- a/java/integTest/src/test/java/glide/standalone/StandaloneClientTests.java +++ b/java/integTest/src/test/java/glide/standalone/StandaloneClientTests.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -@Timeout(10) +@Timeout(10) // seconds public class StandaloneClientTests { @SneakyThrows diff --git a/java/integTest/src/test/java/glide/standalone/TransactionTests.java b/java/integTest/src/test/java/glide/standalone/TransactionTests.java index 8db46436e7..2f9192f99d 100644 --- a/java/integTest/src/test/java/glide/standalone/TransactionTests.java +++ b/java/integTest/src/test/java/glide/standalone/TransactionTests.java @@ -16,13 +16,14 @@ import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; import java.util.UUID; -import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +@Timeout(10) // seconds public class TransactionTests { private static RedisClient client = null; @@ -36,7 +37,7 @@ public static void init() { .address( NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) .build()) - .get(10, TimeUnit.SECONDS); + .get(); } @AfterAll @@ -49,7 +50,7 @@ public static void teardown() { @SneakyThrows public void custom_command_info() { Transaction transaction = new Transaction().customCommand(new String[] {"info"}); - Object[] result = client.exec(transaction).get(10, TimeUnit.SECONDS); + Object[] result = client.exec(transaction).get(); assertTrue(((String) result[0]).contains("# Stats")); } @@ -60,7 +61,7 @@ public void info_test() { new Transaction() .info() .info(InfoOptions.builder().section(InfoOptions.Section.CLUSTER).build()); - Object[] result = client.exec(transaction).get(10, TimeUnit.SECONDS); + Object[] result = client.exec(transaction).get(); // sanity check assertTrue(((String) result[0]).contains("# Stats")); @@ -79,7 +80,7 @@ public void ping_tests() { transaction.ping(Integer.toString(idx)); } } - Object[] result = client.exec(transaction).get(10, TimeUnit.SECONDS); + Object[] result = client.exec(transaction).get(); for (int idx = 0; idx < numberOfPings; idx++) { if ((idx % 2) == 0) { assertEquals("PONG", result[idx]); @@ -106,7 +107,7 @@ public void test_standalone_transactions() { expectedResult = ArrayUtils.addAll(expectedResult, OK, OK, value, OK, null); - Object[] result = client.exec(transaction).get(10, TimeUnit.SECONDS); + Object[] result = client.exec(transaction).get(); assertArrayEquals(expectedResult, result); } } diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 334a5ab6d8..8214fa4251 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -2816,7 +2816,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.21.7 +Package: base64:0.22.0 The following copyrights and licenses were found in the source code of this package: @@ -4038,7 +4038,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.35 +Package: chrono:0.4.37 The following copyrights and licenses were found in the source code of this package: @@ -10092,7 +10092,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.12 +Package: getrandom:0.2.13 The following copyrights and licenses were found in the source code of this package: @@ -13122,7 +13122,7 @@ THIS SOFTWARE. ---- -Package: libredox:0.0.1 +Package: libredox:0.1.3 The following copyrights and licenses were found in the source code of this package: @@ -13813,7 +13813,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: memchr:2.7.1 +Package: memchr:2.7.2 The following copyrights and licenses were found in the source code of this package: @@ -17291,7 +17291,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.13 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -19916,7 +19916,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redox_users:0.4.4 +Package: redox_users:0.4.5 The following copyrights and licenses were found in the source code of this package: @@ -21343,7 +21343,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.1 +Package: rustls-pemfile:2.1.2 The following copyrights and licenses were found in the source code of this package: @@ -21586,7 +21586,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.4.0 +Package: rustls-pki-types:1.4.1 The following copyrights and licenses were found in the source code of this package: @@ -22321,7 +22321,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.9.2 +Package: security-framework:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -22550,7 +22550,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.9.1 +Package: security-framework-sys:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -23522,693 +23522,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: signal-hook:0.3.17 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-registry:1.4.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-tokio:0.3.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: @@ -24977,7 +24290,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.55 +Package: syn:2.0.58 The following copyrights and licenses were found in the source code of this package: @@ -27536,7 +26849,7 @@ the following restrictions: ---- -Package: tokio:1.36.0 +Package: tokio:1.37.0 The following copyrights and licenses were found in the source code of this package: @@ -37339,7 +36652,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.11.30 +Package: @types:node:20.12.5 The following copyrights and licenses were found in the source code of this package: diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 0a44ab9bde..505daa3c05 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -60,6 +60,7 @@ import { createSAdd, createSCard, createSMembers, + createSPop, createSRem, createSet, createSismember, @@ -418,6 +419,13 @@ export class BaseClient { * * @param key - The key to retrieve from the database. * @returns If `key` exists, returns the value of `key` as a string. Otherwise, return null. + * + * @example + * ```typescript + * // Example usage of get method to retrieve the value of a key + * const result = await client.get("key"); + * console.log(result); // Output: 'value' + * ``` */ public get(key: string): Promise { return this.createWritePromise(createGet(key)); @@ -432,6 +440,25 @@ export class BaseClient { * @returns - If the value is successfully set, return OK. * If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist` conditions, return null. * If `returnOldValue` is set, return the old value as a string. + * + * @example + * ```typescript + * // Example usage of set method to set a key-value pair + * const result = await client.set("my_key", "my_value"); + * console.log(result); // Output: 'OK' + * + * // Example usage of set method with conditional options and expiration + * const result2 = await client.set("key", "new_value", {conditionalSet: "onlyIfExists", expiry: { type: "seconds", count: 5 }}); + * console.log(result2); // Output: 'OK' - Set "new_value" to "key" only if "key" already exists, and set the key expiration to 5 seconds. + * + * // Example usage of set method with conditional options and returning old value + * const result3 = await client.set("key", "value", {conditionalSet: "onlyIfDoesNotExist", returnOldValue: true}); + * console.log(result3); // Output: 'new_value' - Returns the old value of "key". + * + * // Example usage of get method to retrieve the value of a key + * const result4 = await client.get("key"); + * console.log(result4); // Output: 'new_value' - Value wasn't modified back to being "value" because of "NX" flag. + * ``` */ public set( key: string, @@ -446,6 +473,21 @@ export class BaseClient { * * @param keys - the keys we wanted to remove. * @returns the number of keys that were removed. + * + * @example + * ```typescript + * // Example usage of del method to delete an existing key + * await client.set("my_key", "my_value"); + * const result = await client.del(["my_key"]); + * console.log(result); // Output: 1 + * ``` + * + * @example + * ```typescript + * // Example usage of del method for a non-existing key + * const result = await client.del(["non_existing_key"]); + * console.log(result); // Output: 0 + * ``` */ public del(keys: string[]): Promise { return this.createWritePromise(createDel(keys)); @@ -457,6 +499,15 @@ export class BaseClient { * @param keys - A list of keys to retrieve values for. * @returns A list of values corresponding to the provided keys. If a key is not found, * its corresponding value in the list will be null. + * + * @example + * ```typescript + * // Example usage of mget method to retrieve values of multiple keys + * await client.set("key1", "value1"); + * await client.set("key2", "value2"); + * const result = await client.mget(["key1", "key2"]); + * console.log(result); // Output: ['value1', 'value2'] + * ``` */ public mget(keys: string[]): Promise<(string | null)[]> { return this.createWritePromise(createMGet(keys)); @@ -467,6 +518,13 @@ export class BaseClient { * * @param keyValueMap - A key-value map consisting of keys and their respective values to set. * @returns always "OK". + * + * @example + * ```typescript + * // Example usage of mset method to set values for multiple keys + * const result = await client.mset({"key1": "value1", "key2": "value2"}); + * console.log(result); // Output: 'OK' + * ``` */ public mset(keyValueMap: Record): Promise<"OK"> { return this.createWritePromise(createMSet(keyValueMap)); @@ -477,6 +535,14 @@ export class BaseClient { * * @param key - The key to increment its value. * @returns the value of `key` after the increment. + * + * @example + * ```typescript + * // Example usage of incr method to increment the value of a key + * await client.set("my_counter", "10"); + * const result = await client.incr("my_counter"); + * console.log(result); // Output: 11 + * ``` */ public incr(key: string): Promise { return this.createWritePromise(createIncr(key)); @@ -488,6 +554,14 @@ export class BaseClient { * @param key - The key to increment its value. * @param amount - The amount to increment. * @returns the value of `key` after the increment. + * + * @example + * ```typescript + * // Example usage of incrBy method to increment the value of a key by a specified amount + * await client.set("my_counter", "10"); + * const result = await client.incrBy("my_counter", 5); + * console.log(result); // Output: 15 + * ``` */ public incrBy(key: string, amount: number): Promise { return this.createWritePromise(createIncrBy(key, amount)); @@ -502,6 +576,13 @@ export class BaseClient { * @param amount - The amount to increment. * @returns the value of `key` after the increment. * + * @example + * ```typescript + * // Example usage of incrByFloat method to increment the value of a floating point key by a specified amount + * await client.set("my_float_counter", "10.5"); + * const result = await client.incrByFloat("my_float_counter", 2.5); + * console.log(result); // Output: 13.0 + * ``` */ public incrByFloat(key: string, amount: number): Promise { return this.createWritePromise(createIncrByFloat(key, amount)); @@ -512,6 +593,14 @@ export class BaseClient { * * @param key - The key to decrement its value. * @returns the value of `key` after the decrement. + * + * @example + * ```typescript + * // Example usage of decr method to decrement the value of a key by 1 + * await client.set("my_counter", "10"); + * const result = await client.decr("my_counter"); + * console.log(result); // Output: 9 + * ``` */ public decr(key: string): Promise { return this.createWritePromise(createDecr(key)); @@ -523,6 +612,14 @@ export class BaseClient { * @param key - The key to decrement its value. * @param amount - The amount to decrement. * @returns the value of `key` after the decrement. + * + * @example + * ```typescript + * // Example usage of decrby method to decrement the value of a key by a specified amount + * await client.set("my_counter", "10"); + * const result = await client.decrby("my_counter", 5); + * console.log(result); // Output: 5 + * ``` */ public decrBy(key: string, amount: number): Promise { return this.createWritePromise(createDecrBy(key, amount)); @@ -534,6 +631,21 @@ export class BaseClient { * @param key - The key of the hash. * @param field - The field in the hash stored at `key` to retrieve from the database. * @returns the value associated with `field`, or null when `field` is not present in the hash or `key` does not exist. + * + * @example + * ```typescript + * // Example usage of the hget method on an-existing field + * await client.hset("my_hash", "field"); + * const result = await client.hget("my_hash", "field"); + * console.log(result); // Output: "value" + * ``` + * + * @example + * ```typescript + * // Example usage of the hget method on a non-existing field + * const result = await client.hget("my_hash", "nonexistent_field"); + * console.log(result); // Output: null + * ``` */ public hget(key: string, field: string): Promise { return this.createWritePromise(createHGet(key, field)); @@ -546,6 +658,13 @@ export class BaseClient { * @param fieldValueMap - A field-value map consisting of fields and their corresponding values * to be set in the hash stored at the specified key. * @returns The number of fields that were added. + * + * @example + * ```typescript + * // Example usage of the hset method + * const result = await client.hset("my_hash", \{"field": "value", "field2": "value2"\}); + * console.log(result); // Output: 2 - Indicates that 2 fields were successfully set in the hash "my_hash". + * ``` */ public hset( key: string, @@ -563,6 +682,20 @@ export class BaseClient { * @param field - The field to set the value for. * @param value - The value to set. * @returns `true` if the field was set, `false` if the field already existed and was not set. + * + * @example + * ```typescript + * // Example usage of the hsetnx method + * const result = await client.hsetnx("my_hash", "field", "value"); + * console.log(result); // Output: true - Indicates that the field "field" was set successfully in the hash "my_hash". + * ``` + * + * @example + * ```typescript + * // Example usage of the hsetnx method on a field that already exists + * const result = await client.hsetnx("my_hash", "field", "new_value"); + * console.log(result); // Output: false - Indicates that the field "field" already existed in the hash "my_hash" and was not set again. + * ``` */ public hsetnx(key: string, field: string, value: string): Promise { return this.createWritePromise(createHSetNX(key, field, value)); @@ -576,6 +709,13 @@ export class BaseClient { * @param fields - The fields to remove from the hash stored at `key`. * @returns the number of fields that were removed from the hash, not including specified but non existing fields. * If `key` does not exist, it is treated as an empty hash and it returns 0. + * + * @example + * ```typescript + * // Example usage of the hdel method + * const result = await client.hdel("my_hash", ["field1", "field2"]); + * console.log(result); // Output: 2 - Indicates that two fields were successfully removed from the hash. + * ``` */ public hdel(key: string, fields: string[]): Promise { return this.createWritePromise(createHDel(key, fields)); @@ -589,6 +729,13 @@ export class BaseClient { * @returns a list of values associated with the given fields, in the same order as they are requested. * For every field that does not exist in the hash, a null value is returned. * If `key` does not exist, it is treated as an empty hash and it returns a list of null values. + * + * @example + * ```typescript + * // Example usage of the hmget method + * const result = await client.hmget("my_hash", ["field1", "field2"]); + * console.log(result); // Output: ["value1", "value2"] - A list of values associated with the specified fields. + * ``` */ public hmget(key: string, fields: string[]): Promise<(string | null)[]> { return this.createWritePromise(createHMGet(key, fields)); @@ -600,6 +747,20 @@ export class BaseClient { * @param key - The key of the hash. * @param field - The field to check in the hash stored at `key`. * @returns `true` the hash contains `field`. If the hash does not contain `field`, or if `key` does not exist, it returns `false`. + * + * @example + * ```typescript + * // Example usage of the hexists method with existing field + * const result = await client.hexists("my_hash", "field1"); + * console.log(result); // Output: true + * ``` + * + * @example + * ```typescript + * // Example usage of the hexists method with non-existing field + * const result = await client.hexists("my_hash", "nonexistent_field"); + * console.log(result); // Output: false + * ``` */ public hexists(key: string, field: string): Promise { return this.createWritePromise(createHExists(key, field)); @@ -611,6 +772,13 @@ export class BaseClient { * @param key - The key of the hash. * @returns a list of fields and their values stored in the hash. Every field name in the list is followed by its value. * If `key` does not exist, it returns an empty list. + * + * @example + * ```typescript + * // Example usage of the hgetall method + * const result = await client.hgetall("my_hash"); + * console.log(result); // Output: {"field1": "value1", "field2": "value2"} + * ``` */ public hgetall(key: string): Promise> { return this.createWritePromise(createHGetAll(key)); @@ -625,6 +793,13 @@ export class BaseClient { * @param amount - The amount to increment. * @param field - The field in the hash stored at `key` to increment its value. * @returns the value of `field` in the hash stored at `key` after the increment. + * + * @example + * ```typescript + * // Example usage of the hincrby method to increment the value in a hash by a specified amount + * const result = await client.hincrby("my_hash", "field1", 5); + * console.log(result); // Output: 5 + * ``` */ public hincrBy( key: string, @@ -643,6 +818,13 @@ export class BaseClient { * @param amount - The amount to increment. * @param field - The field in the hash stored at `key` to increment its value. * @returns the value of `field` in the hash stored at `key` after the increment. + * + * @example + * ```typescript + * // Example usage of the hincrbyfloat method to increment the value of a floating point in a hash by a specified amount + * const result = await client.hincrbyfloat("my_hash", "field1", 2.5); + * console.log(result); // Output: '2.5' + * ``` */ public hincrByFloat( key: string, @@ -657,6 +839,20 @@ export class BaseClient { * * @param key - The key of the hash. * @returns The number of fields in the hash, or 0 when the key does not exist. + * + * @example + * ```typescript + * // Example usage of the hlen method with an existing key + * const result = await client.hlen("my_hash"); + * console.log(result); // Output: 3 + * ``` + * + * @example + * ```typescript + * // Example usage of the hlen method with a non-existing key + * const result = await client.hlen("non_existing_key"); + * console.log(result); // Output: 0 + * ``` */ public hlen(key: string): Promise { return this.createWritePromise(createHLen(key)); @@ -667,6 +863,13 @@ export class BaseClient { * * @param key - The key of the hash. * @returns a list of values in the hash, or an empty list when the key does not exist. + * + * @example + * ```typescript + * // Example usage of the hvals method + * const result = await client.hvals("my_hash"); + * console.log(result); // Output: ["value1", "value2", "value3"] - Returns all the values stored in the hash "my_hash". + * ``` */ public hvals(key: string): Promise { return this.createWritePromise(createHvals(key)); @@ -680,6 +883,20 @@ export class BaseClient { * @param key - The key of the list. * @param elements - The elements to insert at the head of the list stored at `key`. * @returns the length of the list after the push operations. + * + * @example + * ```typescript + * // Example usage of the lpush method with an existing list + * const result = await client.lpush("my_list", ["value2", "value3"]); + * console.log(result); // Output: 3 - Indicated that the new length of the list is 3 after the push operation. + * ``` + * + * @example + * ```typescript + * // Example usage of the lpush method with a non-existing list + * const result = await client.lpush("nonexistent_list", ["new_value"]); + * console.log(result); // Output: 1 - Indicates that a new list was created with one element + * ``` */ public lpush(key: string, elements: string[]): Promise { return this.createWritePromise(createLPush(key, elements)); @@ -692,6 +909,20 @@ export class BaseClient { * @param key - The key of the list. * @returns The value of the first element. * If `key` does not exist null will be returned. + * + * @example + * ```typescript + * // Example usage of the lpop method with an existing list + * const result = await client.lpop("my_list"); + * console.log(result); // Output: 'value1' + * ``` + * + * @example + * ```typescript + * // Example usage of the lpop method with a non-existing list + * const result = await client.lpop("non_exiting_key"); + * console.log(result); // Output: null + * ``` */ public lpop(key: string): Promise { return this.createWritePromise(createLPop(key)); @@ -704,6 +935,20 @@ export class BaseClient { * @param count - The count of the elements to pop from the list. * @returns A list of the popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. + * + * @example + * ```typescript + * // Example usage of the lpopCount method with an existing list + * const result = await client.lpopCount("my_list", 2); + * console.log(result); // Output: ["value1", "value2"] + * ``` + * + * @example + * ```typescript + * // Example usage of the lpopCount method with a non-existing list + * const result = await client.lpopCount("non_exiting_key", 3); + * console.log(result); // Output: null + * ``` */ public lpopCount(key: string, count: number): Promise { return this.createWritePromise(createLPop(key, count)); @@ -722,6 +967,27 @@ export class BaseClient { * If `start` exceeds the end of the list, or if `start` is greater than `end`, an empty list will be returned. * If `end` exceeds the actual end of the list, the range will stop at the actual end of the list. * If `key` does not exist an empty list will be returned. + * + * @example + * ```typescript + * // Example usage of the lrange method with an existing list and positive indices + * const result = await client.lrange("my_list", 0, 2); + * console.log(result); // Output: ["value1", "value2", "value3"] + * ``` + * + * @example + * ```typescript + * // Example usage of the lrange method with an existing list and negative indices + * const result = await client.lrange("my_list", -2, -1); + * console.log(result); // Output: ["value2", "value3"] + * ``` + * + * @example + * ```typescript + * // Example usage of the lrange method with a non-existing list + * const result = await client.lrange("non_exiting_key", 0, 2); + * console.log(result); // Output: [] + * ``` */ public lrange(key: string, start: number, end: number): Promise { return this.createWritePromise(createLRange(key, start, end)); @@ -733,6 +999,13 @@ export class BaseClient { * @param key - The key of the list. * @returns the length of the list at `key`. * If `key` does not exist, it is interpreted as an empty list and 0 is returned. + * + * @example + * ```typescript + * // Example usage of the llen method + * const result = await client.llen("my_list"); + * console.log(result); // Output: 3 - Indicates that there are 3 elements in the list. + * ``` */ public llen(key: string): Promise { return this.createWritePromise(createLLen(key)); @@ -751,6 +1024,13 @@ export class BaseClient { * If `start` exceeds the end of the list, or if `start` is greater than `end`, the result will be an empty list (which causes key to be removed). * If `end` exceeds the actual end of the list, it will be treated like the last element of the list. * If `key` does not exist the command will be ignored. + * + * @example + * ```typescript + * // Example usage of the ltrim method + * const result = await client.ltrim("my_list", 0, 1); + * console.log(result); // Output: 'OK' - Indicates that the list has been trimmed to contain elements from 0 to 1. + * ``` */ public ltrim(key: string, start: number, end: number): Promise<"OK"> { return this.createWritePromise(createLTrim(key, start, end)); @@ -766,6 +1046,13 @@ export class BaseClient { * @param element - The element to remove from the list. * @returns the number of the removed elements. * If `key` does not exist, 0 is returned. + * + * @example + * ```typescript + * // Example usage of the lrem method + * const result = await client.lrem("my_list", 2, "value"); + * console.log(result); // Output: 2 - Removes the first 2 occurrences of "value" in the list. + * ``` */ public lrem(key: string, count: number, element: string): Promise { return this.createWritePromise(createLRem(key, count, element)); @@ -779,6 +1066,20 @@ export class BaseClient { * @param key - The key of the list. * @param elements - The elements to insert at the tail of the list stored at `key`. * @returns the length of the list after the push operations. + * + * @example + * ```typescript + * // Example usage of the rpush method with an existing list + * const result = await client.rpush("my_list", ["value2", "value3"]); + * console.log(result); // Output: 3 - Indicates that the new length of the list is 3 after the push operation. + * ``` + * + * @example + * ```typescript + * // Example usage of the rpush method with a non-existing list + * const result = await client.rpush("nonexistent_list", ["new_value"]); + * console.log(result); // Output: 1 + * ``` */ public rpush(key: string, elements: string[]): Promise { return this.createWritePromise(createRPush(key, elements)); @@ -791,6 +1092,20 @@ export class BaseClient { * @param key - The key of the list. * @returns The value of the last element. * If `key` does not exist null will be returned. + * + * @example + * ```typescript + * // Example usage of the rpop method with an existing list + * const result = await client.rpop("my_list"); + * console.log(result); // Output: 'value1' + * ``` + * + * @example + * ```typescript + * // Example usage of the rpop method with a non-existing list + * const result = await client.rpop("non_exiting_key"); + * console.log(result); // Output: null + * ``` */ public rpop(key: string): Promise { return this.createWritePromise(createRPop(key)); @@ -803,6 +1118,20 @@ export class BaseClient { * @param count - The count of the elements to pop from the list. * @returns A list of popped elements will be returned depending on the list's length. * If `key` does not exist null will be returned. + * + * @example + * ```typescript + * // Example usage of the rpopCount method with an existing list + * const result = await client.rpopCount("my_list", 2); + * console.log(result); // Output: ["value1", "value2"] + * ``` + * + * @example + * ```typescript + * // Example usage of the rpopCount method with a non-existing list + * const result = await client.rpopCount("non_exiting_key", 7); + * console.log(result); // Output: null + * ``` */ public rpopCount(key: string, count: number): Promise { return this.createWritePromise(createRPop(key, count)); @@ -814,7 +1143,14 @@ export class BaseClient { * * @param key - The key to store the members to its set. * @param members - A list of members to add to the set stored at `key`. - * @returns the number of members that were added to the set, not including all the members already present in the set. + * @returns The number of members that were added to the set, not including all the members already present in the set. + * + * @example + * ```typescript + * // Example usage of the sadd method with an existing set + * const result = await client.sadd("my_set", ["member1", "member2"]); + * console.log(result); // Output: 2 + * ``` */ public sadd(key: string, members: string[]): Promise { return this.createWritePromise(createSAdd(key, members)); @@ -825,8 +1161,15 @@ export class BaseClient { * * @param key - The key to remove the members from its set. * @param members - A list of members to remove from the set stored at `key`. - * @returns the number of members that were removed from the set, not including non existing members. + * @returns The number of members that were removed from the set, not including non existing members. * If `key` does not exist, it is treated as an empty set and this command returns 0. + * + * @example + * ```typescript + * // Example usage of the srem method + * const result = await client.srem("my_set", ["member1", "member2"]); + * console.log(result); // Output: 2 + * ``` */ public srem(key: string, members: string[]): Promise { return this.createWritePromise(createSRem(key, members)); @@ -836,8 +1179,15 @@ export class BaseClient { * See https://redis.io/commands/smembers/ for details. * * @param key - The key to return its members. - * @returns all members of the set. + * @returns All members of the set. * If `key` does not exist, it is treated as an empty set and this command returns empty list. + * + * @example + * ```typescript + * // Example usage of the smembers method + * const result = await client.smembers("my_set"); + * console.log(result); // Output: ["member1", "member2", "member3"] + * ``` */ public smembers(key: string): Promise { return this.createWritePromise(createSMembers(key)); @@ -847,7 +1197,14 @@ export class BaseClient { * See https://redis.io/commands/scard/ for details. * * @param key - The key to return the number of its members. - * @returns the cardinality (number of elements) of the set, or 0 if key does not exist. + * @returns The cardinality (number of elements) of the set, or 0 if key does not exist. + * + * @example + * ```typescript + * // Example usage of the scard method + * const result = await client.scard("my_set"); + * console.log(result); // Output: 3 + * ``` */ public scard(key: string): Promise { return this.createWritePromise(createSCard(key)); @@ -860,17 +1217,62 @@ export class BaseClient { * @param member - The member to check for existence in the set. * @returns `true` if the member exists in the set, `false` otherwise. * If `key` doesn't exist, it is treated as an empty set and the command returns `false`. + * + * @example + * ```typescript + * // Example usage of the sismember method when member exists + * const result = await client.sismember("my_set", "member1"); + * console.log(result); // Output: true - Indicates that "member1" exists in the set "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of the sismember method when member does not exist + * const result = await client.sismember("my_set", "non_existing_member"); + * console.log(result); // Output: false - Indicates that "non_existing_member" does not exist in the set "my_set". + * ``` */ public sismember(key: string, member: string): Promise { return this.createWritePromise(createSismember(key, member)); } + /** Removes and returns one random member from the set value store at `key`. + * See https://redis.io/commands/spop/ for details. + * To pop multiple members, see `spopCount`. + * + * @param key - The key of the set. + * @returns the value of the popped member. + * If `key` does not exist, null will be returned. + */ + public spop(key: string): Promise { + return this.createWritePromise(createSPop(key)); + } + + /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length. + * See https://redis.io/commands/spop/ for details. + * + * @param key - The key of the set. + * @param count - The count of the elements to pop from the set. + * @returns A list of popped elements will be returned depending on the set's length. + * If `key` does not exist, empty list will be returned. + */ + public spopCount(key: string, count: number): Promise { + return this.createWritePromise(createSPop(key, count)); + } + /** Returns the number of keys in `keys` that exist in the database. * See https://redis.io/commands/exists/ for details. * * @param keys - The keys list to check. - * @returns the number of keys that exist. If the same existing key is mentioned in `keys` multiple times, + * @returns The number of keys that exist. If the same existing key is mentioned in `keys` multiple times, * it will be counted multiple times. + * + * @example + * ```typescript + * // Example usage of the exists method + * const result = await client.exists(["key1", "key2", "key3"]); + * console.log(result); // Output: 3 - Indicates that all three keys exist in the database. + * ``` */ public exists(keys: string[]): Promise { return this.createWritePromise(createExists(keys)); @@ -882,7 +1284,14 @@ export class BaseClient { * See https://redis.io/commands/unlink/ for details. * * @param keys - The keys we wanted to unlink. - * @returns the number of keys that were unlinked. + * @returns The number of keys that were unlinked. + * + * @example + * ```typescript + * // Example usage of the unlink method + * const result = await client.unlink(["key1", "key2", "key3"]); + * console.log(result); // Output: 3 - Indicates that all three keys were unlinked from the database. + * ``` */ public unlink(keys: string[]): Promise { return this.createWritePromise(createUnlink(keys)); @@ -899,6 +1308,20 @@ export class BaseClient { * @param option - The expire option. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. + * + * @example + * ```typescript + * // Example usage of the expire method + * const result = await client.expire("my_key", 60); + * console.log(result); // Output: true - Indicates that a timeout of 60 seconds has been set for "my_key". + * ``` + * + * @example + * ```typescript + * // Example usage of the expire method with exisiting expiry + * const result = await client.expire("my_key", 60, ExpireOptions.HasNoExpiry); + * console.log(result); // Output: false - Indicates that "my_key" has an existing expiry. + * ``` */ public expire( key: string, @@ -919,6 +1342,13 @@ export class BaseClient { * @param option - The expire option. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. + * + * @example + * ```typescript + * // Example usage of the expireAt method on a key with no previous expiry + * const result = await client.expireAt("my_key", 1672531200, ExpireOptions.HasNoExpiry); + * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. + * ``` */ public expireAt( key: string, @@ -941,6 +1371,13 @@ export class BaseClient { * @param option - The expire option. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. + * + * @example + * ```typescript + * // Example usage of the pexpire method on a key with no previous expiry + * const result = await client.pexpire("my_key", 60000, ExpireOptions.HasNoExpiry); + * console.log(result); // Output: true - Indicates that a timeout of 60,000 milliseconds has been set for "my_key". + * ``` */ public pexpire( key: string, @@ -963,6 +1400,13 @@ export class BaseClient { * @param option - The expire option. * @returns `true` if the timeout was set. `false` if the timeout was not set. e.g. key doesn't exist, * or operation skipped due to the provided arguments. + * + * @example + * ```typescript + * // Example usage of the pexpireAt method on a key with no previous expiry + * const result = await client.pexpireAt("my_key", 1672531200000, ExpireOptions.HasNoExpiry); + * console.log(result); // Output: true - Indicates that the expiration time for "my_key" was successfully set. + * ``` */ public pexpireAt( key: string, @@ -979,6 +1423,27 @@ export class BaseClient { * * @param key - The key to return its timeout. * @returns TTL in seconds, -2 if `key` does not exist or -1 if `key` exists but has no associated expire. + * + * @example + * ```typescript + * // Example usage of the ttl method with existing key + * const result = await client.ttl("my_key"); + * console.log(result); // Output: 3600 - Indicates that "my_key" has a remaining time to live of 3600 seconds. + * ``` + * + * @example + * ```typescript + * // Example usage of the ttl method with existing key that has no associated expire. + * const result = await client.ttl("key"); + * console.log(result); // Output: -1 - Indicates that the key has no associated expire. + * ``` + * + * @example + * ```typescript + * // Example usage of the ttl method with a non-existing key + * const result = await client.ttl("nonexistent_key"); + * console.log(result); // Output: -2 - Indicates that the key doesn't exist. + * ``` */ public ttl(key: string): Promise { return this.createWritePromise(createTTL(key)); @@ -1027,12 +1492,18 @@ export class BaseClient { * If `changed` is set, returns the number of elements updated in the sorted set. * * @example - * await client.zadd("mySortedSet", \{ "member1": 10.5, "member2": 8.2 \}) - * 2 (Indicates that two elements have been added to the sorted set "mySortedSet".) - * - * await client.zadd("existingSortedSet", \{ member1: 15.0, member2: 5.5 \}, \{ conditionalChange: "onlyIfExists" \} , true); - * 2 (Updates the scores of two existing members in the sorted set "existingSortedSet".) + * ```typescript + * // Example usage of the zadd method to add elements to a sorted set + * const result = await client.zadd("my_sorted_set", \{ "member1": 10.5, "member2": 8.2 \}); + * console.log(result); // Output: 2 - Indicates that two elements have been added to the sorted set "my_sorted_set." + * ``` * + * @example + * ```typescript + * // Example usage of the zadd method to update scores in an existing sorted set + * const result = await client.zadd("existing_sorted_set", { member1: 15.0, member2: 5.5 }, options={ conditionalChange: "onlyIfExists" } , changed=true); + * console.log(result); // Output: 2 - Updates the scores of two existing members in the sorted set "existing_sorted_set." + * ``` */ public zadd( key: string, @@ -1063,11 +1534,18 @@ export class BaseClient { * If there was a conflict with the options, the operation aborts and null is returned. * * @example - * await client.zaddIncr("mySortedSet", member , 5.0) - * 5.0 + * ```typescript + * // Example usage of the zaddIncr method to add a member with a score to a sorted set + * const result = await client.zaddIncr("my_sorted_set", member, 5.0); + * console.log(result); // Output: 5.0 + * ``` * - * await client.zaddIncr("existingSortedSet", member , "3.0" , \{ UpdateOptions: "ScoreLessThanCurrent" \}) - * null + * @example + * ```typescript + * // Example usage of the zaddIncr method to add or update a member with a score in an existing sorted set + * const result = await client.zaddIncr("existing_sorted_set", member, "3.0", { UpdateOptions: "ScoreLessThanCurrent" }); + * console.log(result); // Output: null - Indicates that the member in the sorted set haven't been updated. + * ``` */ public zaddIncr( key: string, @@ -1088,6 +1566,20 @@ export class BaseClient { * @param members - A list of members to remove from the sorted set. * @returns The number of members that were removed from the sorted set, not including non-existing members. * If `key` does not exist, it is treated as an empty sorted set, and this command returns 0. + * + * @example + * ```typescript + * // Example usage of the zrem function to remove members from a sorted set + * const result = await client.zrem("my_sorted_set", ["member1", "member2"]); + * console.log(result); // Output: 2 - Indicates that two members have been removed from the sorted set "my_sorted_set." + * ``` + * + * @example + * ```typescript + * // Example usage of the zrem function when the sorted set does not exist + * const result = await client.zrem("non_existing_sorted_set", ["member1", "member2"]); + * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. + * ``` */ public zrem(key: string, members: string[]): Promise { return this.createWritePromise(createZrem(key, members)); @@ -1099,6 +1591,20 @@ export class BaseClient { * @param key - The key of the sorted set. * @returns The number of elements in the sorted set. * If `key` does not exist, it is treated as an empty sorted set, and this command returns 0. + * + * @example + * ```typescript + * // Example usage of the zcard method to get the cardinality of a sorted set + * const result = await client.zcard("my_sorted_set"); + * console.log(result); // Output: 3 - Indicates that there are 3 elements in the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of the zcard method with a non-existing key + * const result = await client.zcard("non_existing_key"); + * console.log(result); // Output: 0 + * ``` */ public zcard(key: string): Promise { return this.createWritePromise(createZcard(key)); @@ -1112,6 +1618,27 @@ export class BaseClient { * @returns The score of the member. * If `member` does not exist in the sorted set, null is returned. * If `key` does not exist, null is returned. + * + * @example + * ```typescript + * // Example usage of the zscore method∂∂ to get the score of a member in a sorted set + * const result = await client.zscore("my_sorted_set", "member"); + * console.log(result); // Output: 10.5 - Indicates that the score of "member" in the sorted set "my_sorted_set" is 10.5. + * ``` + * + * @example + * ```typescript + * // Example usage of the zscore method when the member does not exist in the sorted set + * const result = await client.zscore("my_sorted_set", "non_existing_member"); + * console.log(result); // Output: null + * ``` + * + * @example + * ```typescript + * // Example usage of the zscore method with non existimng key + * const result = await client.zscore("non_existing_set", "member"); + * console.log(result); // Output: null + * ``` */ public zscore(key: string, member: string): Promise { return this.createWritePromise(createZscore(key, member)); @@ -1126,6 +1653,20 @@ export class BaseClient { * @returns The number of members in the specified score range. * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. * If `minScore` is greater than `maxScore`, 0 is returned. + * + * @example + * ```typescript + * // Example usage of the zcount method to count members in a sorted set within a score range + * const result = await client.zcount("my_sorted_set", { bound: 5.0, isInclusive: true }, "positiveInfinity"); + * console.log(result); // Output: 2 - Indicates that there are 2 members with scores between 5.0 (inclusive) and +inf in the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of the zcount method to count members in a sorted set within a score range + * const result = await client.zcount("my_sorted_set", { bound: 5.0, isInclusive: true }, { bound: 10.0, isInclusive: false }); + * console.log(result); // Output: 1 - Indicates that there is one member with score between 5.0 (inclusive) and 10.0 (exclusive) in the sorted set "my_sorted_set". + * ``` */ public zcount( key: string, @@ -1141,6 +1682,21 @@ export class BaseClient { * @param key - The key to check its length. * @returns - The length of the string value stored at key * If `key` does not exist, it is treated as an empty string, and the command returns 0. + * + * @example + * ```typescript + * // Example usage of strlen method with an existing key + * await client.set("key", "GLIDE"); + * const len1 = await client.strlen("key"); + * console.log(len1); // Output: 5 + * ``` + * + * @example + * ```typescript + * // Example usage of strlen method with a non-existing key + * const len2 = await client.strlen("non_existing_key"); + * console.log(len2); // Output: 0 + * ``` */ public strlen(key: string): Promise { return this.createWritePromise(createStrlen(key)); @@ -1151,6 +1707,22 @@ export class BaseClient { * * @param key - The `key` to check its data type. * @returns If the `key` exists, the type of the stored value is returned. Otherwise, a "none" string is returned. + * + * @example + * ```typescript + * // Example usage of type method with a string value + * await client.set("key", "value"); + * const type = await client.type("key"); + * console.log(type); // Output: 'string' + * ``` + * + * @example + * ```typescript + * // Example usage of type method with a list + * await client.lpush("key", ["value"]); + * const type = await client.type("key"); + * console.log(type); // Output: 'list' + * ``` */ public type(key: string): Promise { return this.createWritePromise(createType(key)); @@ -1166,6 +1738,20 @@ export class BaseClient { * @returns A map of the removed members and their scores, ordered from the one with the lowest score to the one with the highest. * If `key` doesn't exist, it will be treated as an empty sorted set and the command returns an empty map. * If `count` is higher than the sorted set's cardinality, returns all members and their scores. + * + * @example + * ```typescript + * // Example usage of zpopmin method to remove and return the member with the lowest score from a sorted set + * const result = await client.zpopmin("my_sorted_set"); + * console.log(result); // Output: {'member1': 5.0} - Indicates that 'member1' with a score of 5.0 has been removed from the sorted set. + * ``` + * + * @example + * ```typescript + * // Example usage of zpopmin method to remove and return multiple members with the lowest scores from a sorted set + * const result = await client.zpopmin("my_sorted_set", 2); + * console.log(result); // Output: {'member3': 7.5 , 'member2': 8.0} - Indicates that 'member3' with a score of 7.5 and 'member2' with a score of 8.0 have been removed from the sorted set. + * ``` */ public zpopmin( key: string, @@ -1184,6 +1770,20 @@ export class BaseClient { * @returns A map of the removed members and their scores, ordered from the one with the highest score to the one with the lowest. * If `key` doesn't exist, it will be treated as an empty sorted set and the command returns an empty map. * If `count` is higher than the sorted set's cardinality, returns all members and their scores, ordered from highest to lowest. + * + * @example + * ```typescript + * // Example usage of zpopmax method to remove and return the member with the highest score from a sorted set + * const result = await client.zpopmax("my_sorted_set"); + * console.log(result); // Output: {'member1': 10.0} - Indicates that 'member1' with a score of 10.0 has been removed from the sorted set. + * ``` + * + * @example + * ```typescript + * // Example usage of zpopmax method to remove and return multiple members with the highest scores from a sorted set + * const result = await client.zpopmax("my_sorted_set", 2); + * console.log(result); // Output: {'member2': 8.0, 'member3': 7.5} - Indicates that 'member2' with a score of 8.0 and 'member3' with a score of 7.5 have been removed from the sorted set. + * ``` */ public zpopmax( key: string, @@ -1197,6 +1797,27 @@ export class BaseClient { * * @param key - The key to return its timeout. * @returns TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + * + * @example + * ```typescript + * // Example usage of pttl method with an existing key + * const result = await client.pttl("my_key"); + * console.log(result); // Output: 5000 - Indicates that the key "my_key" has a remaining time to live of 5000 milliseconds. + * ``` + * + * @example + * ```typescript + * // Example usage of pttl method with a non-existing key + * const result = await client.pttl("non_existing_key"); + * console.log(result); // Output: -2 - Indicates that the key "non_existing_key" does not exist. + * ``` + * + * @example + * ```typescript + * // Example usage of pttl method with an exisiting key that has no associated expire. + * const result = await client.pttl("key"); + * console.log(result); // Output: -1 - Indicates that the key "key" has no associated expire. + * ``` */ public pttl(key: string): Promise { return this.createWritePromise(createPttl(key)); @@ -1214,6 +1835,13 @@ export class BaseClient { * If `start` exceeds the end of the sorted set, or if `start` is greater than `end`, 0 returned. * If `end` exceeds the actual end of the sorted set, the range will stop at the actual end of the sorted set. * If `key` does not exist 0 will be returned. + * + * @example + * ```typescript + * // Example usage of zremRangeByRank method + * const result = await client.zremRangeByRank("my_sorted_set", 0, 2); + * console.log(result); // Output: 3 - Indicates that three elements have been removed from the sorted set "my_sorted_set" between ranks 0 and 2. + * ``` */ public zremRangeByRank( key: string, @@ -1232,6 +1860,20 @@ export class BaseClient { * @returns the number of members removed. * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. * If `minScore` is greater than `maxScore`, 0 is returned. + * + * @example + * ```typescript + * // Example usage of zremRangeByScore method to remove members from a sorted set based on score range + * const result = await client.zremRangeByScore("my_sorted_set", { bound: 5.0, isInclusive: true }, "positiveInfinity"); + * console.log(result); // Output: 2 - Indicates that 2 members with scores between 5.0 (inclusive) and +inf have been removed from the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of zremRangeByScore method when the sorted set does not exist + * const result = await client.zremRangeByScore("non_existing_sorted_set", { bound: 5.0, isInclusive: true }, { bound: 10.0, isInclusive: false }); + * console.log(result); // Output: 0 - Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. + * ``` */ public zremRangeByScore( key: string, @@ -1251,6 +1893,20 @@ export class BaseClient { * @param member - The member whose rank is to be retrieved. * @returns The rank of `member` in the sorted set. * If `key` doesn't exist, or if `member` is not present in the set, null will be returned. + * + * @example + * ```typescript + * // Example usage of zrank method to retrieve the rank of a member in a sorted set + * const result = await client.zrank("my_sorted_set", "member2"); + * console.log(result); // Output: 1 - Indicates that "member2" has the second-lowest score in the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of zrank method with a non-existing member + * const result = await client.zrank("my_sorted_set", "non_existing_member"); + * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". + * ``` */ public zrank(key: string, member: string): Promise { return this.createWritePromise(createZrank(key, member)); @@ -1265,6 +1921,20 @@ export class BaseClient { * If `key` doesn't exist, or if `member` is not present in the set, null will be returned. * * since - Redis version 7.2.0. + * + * @example + * ```typescript + * // Example usage of zrank_withscore method to retrieve the rank and score of a member in a sorted set + * const result = await client.zrank_withscore("my_sorted_set", "member2"); + * console.log(result); // Output: [1, 6.0] - Indicates that "member2" with score 6.0 has the second-lowest score in the sorted set "my_sorted_set". + * ``` + * + * @example + * ```typescript + * // Example usage of zrank_withscore method with a non-existing member + * const result = await client.zrank_withscore("my_sorted_set", "non_existing_member"); + * console.log(result); // Output: null - Indicates that "non_existing_member" is not present in the sorted set "my_sorted_set". + * ``` */ public zrankWithScore( key: string, @@ -1334,6 +2004,20 @@ export class BaseClient { * @param index - The `index` of the element in the list to retrieve. * @returns - The element at `index` in the list stored at `key`. * If `index` is out of range or if `key` does not exist, null is returned. + * + * @example + * ```typescript + * // Example usage of lindex method to retrieve elements from a list by index + * const result = await client.lindex("my_list", 0); + * console.log(result); // Output: 'value1' - Returns the first element in the list stored at 'my_list'. + * ``` + * + * @example + * ```typescript + * // Example usage of lindex method to retrieve elements from a list by negative index + * const result = await client.lindex("my_list", -1); + * console.log(result); // Output: 'value3' - Returns the last element in the list stored at 'my_list'. + * ``` */ public lindex(key: string, index: number): Promise { return this.createWritePromise(createLindex(key, index)); @@ -1345,6 +2029,13 @@ export class BaseClient { * * @param key - The key to remove the existing timeout on. * @returns `false` if `key` does not exist or does not have an associated timeout, `true` if the timeout has been removed. + * + * @example + * ```typescript + * // Example usage of persist method to remove the timeout associated with a key + * const result = await client.persist("my_key"); + * console.log(result); // Output: true - Indicates that the timeout associated with the key "my_key" was successfully removed. + * ``` */ public persist(key: string): Promise { return this.createWritePromise(createPersist(key)); @@ -1360,6 +2051,14 @@ export class BaseClient { * @param key - The key to rename. * @param newKey - The new name of the key. * @returns - If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown. + * + * @example + * ```typescript + * // Example usage of rename method to rename a key + * await client.set("old_key", "value"); + * const result = await client.rename("old_key", "new_key"); + * console.log(result); // Output: OK - Indicates successful renaming of the key "old_key" to "new_key". + * ``` */ public rename(key: string, newKey: string): Promise<"OK"> { return this.createWritePromise(createRename(key, newKey)); @@ -1380,8 +2079,9 @@ export class BaseClient { * * @example * ```typescript + * // Example usage of brpop method to block and wait for elements from multiple lists * const result = await client.brpop(["list1", "list2"], 5); - * console.log(result); // Output: ['list1', 'element'] + * console.log(result); // Output: ["list1", "element"] - Indicates an element "element" was popped from "list1". * ``` */ public brpop( @@ -1406,8 +2106,9 @@ export class BaseClient { * * @example * ```typescript + * // Example usage of blpop method to block and wait for elements from multiple lists * const result = await client.blpop(["list1", "list2"], 5); - * console.log(result); // Output: ['list1', 'element'] + * console.log(result); // Output: ["list1", "element"] - Indicates an element "element" was popped from "list1". * ``` */ public blpop( diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 302108a05f..cccb2ebede 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -566,6 +566,14 @@ export function createSismember( return createCommand(RequestType.SIsMember, [key, member]); } +/** + * @internal + */ +export function createSPop(key: string, count?: number): redis_request.Command { + const args: string[] = count == undefined ? [key] : [key, count.toString()]; + return createCommand(RequestType.Spop, args); +} + /** * @internal */ diff --git a/node/src/RedisClient.ts b/node/src/RedisClient.ts index 0d55793a94..cfa6db50b5 100644 --- a/node/src/RedisClient.ts +++ b/node/src/RedisClient.ts @@ -111,9 +111,10 @@ export class RedisClient extends BaseClient { * @remarks - This function should only be used for single-response commands. Commands that don't return response (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. * * @example - * Returns a list of all pub/sub clients: - * ```ts - * connection.customCommand(["CLIENT","LIST","TYPE", "PUBSUB"]) + * ```typescript + * // Example usage of customCommand method to retrieve pub/sub clients + * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"]); + * console.log(result); // Output: Returns a list of all pub/sub clients * ``` */ public customCommand(args: string[]): Promise { @@ -127,6 +128,20 @@ export class RedisClient extends BaseClient { * If not provided, the server will respond with "PONG". * If provided, the server will respond with a copy of the message. * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`. + * + * @example + * ```typescript + * // Example usage of ping method without any message + * const result = await client.ping(); + * console.log(result); // Output: 'PONG' + * ``` + * + * @example + * ```typescript + * // Example usage of ping method with a message + * const result = await client.ping("Hello"); + * console.log(result); // Output: 'Hello' + * ``` */ public ping(message?: string): Promise { return this.createWritePromise(createPing(message)); @@ -148,6 +163,13 @@ export class RedisClient extends BaseClient { * * @param index - The index of the database to select. * @returns A simple OK response. + * + * @example + * ```typescript + * // Example usage of select method + * const result = await client.select(2); + * console.log(result); // Output: 'OK' + * ``` */ public select(index: number): Promise<"OK"> { return this.createWritePromise(createSelect(index)); @@ -157,6 +179,13 @@ export class RedisClient extends BaseClient { * See https://redis.io/commands/client-getname/ for more details. * * @returns the name of the client connection as a string if a name is set, or null if no name is assigned. + * + * @example + * ```typescript + * // Example usage of client_getname method + * const result = await client.client_getname(); + * console.log(result); // Output: 'Client Name' + * ``` */ public clientGetName(): Promise { return this.createWritePromise(createClientGetName()); @@ -166,6 +195,13 @@ export class RedisClient extends BaseClient { * See https://redis.io/commands/config-rewrite/ for details. * * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown. + * + * @example + * ```typescript + * // Example usage of configRewrite command + * const result = await client.configRewrite(); + * console.log(result); // Output: 'OK' + * ``` */ public configRewrite(): Promise<"OK"> { return this.createWritePromise(createConfigRewrite()); @@ -175,6 +211,13 @@ export class RedisClient extends BaseClient { * See https://redis.io/commands/config-resetstat/ for details. * * @returns always "OK". + * + * @example + * ```typescript + * // Example usage of configResetStat command + * const result = await client.configResetStat(); + * console.log(result); // Output: 'OK' + * ``` */ public configResetStat(): Promise<"OK"> { return this.createWritePromise(createConfigResetStat()); @@ -196,6 +239,12 @@ export class RedisClient extends BaseClient { * * @returns A map of values corresponding to the configuration parameters. * + * @example + * ```typescript + * // Example usage of configGet method with multiple configuration parameters + * const result = await client.configGet(["timeout", "maxmemory"]); + * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'} + * ``` */ public configGet(parameters: string[]): Promise> { return this.createWritePromise(createConfigGet(parameters)); @@ -209,8 +258,11 @@ export class RedisClient extends BaseClient { * @returns "OK" when the configuration was set properly. Otherwise an error is thrown. * * @example - * config_set([("timeout", "1000")], [("maxmemory", "1GB")]) - Returns OK - * + * ```typescript + * // Example usage of configSet method to set multiple configuration parameters + * const result = await client.configSet({ timeout: "1000", maxmemory, "1GB" }); + * console.log(result); // Output: 'OK' + * ``` */ public configSet(parameters: Record): Promise<"OK"> { return this.createWritePromise(createConfigSet(parameters)); @@ -226,7 +278,7 @@ export class RedisClient extends BaseClient { * ```typescript * // Example usage of the echo command * const echoedMessage = await client.echo("Glide-for-Redis"); - * console.log(echoedMessage); // Output: "Glide-for-Redis" + * console.log(echoedMessage); // Output: 'Glide-for-Redis' * ``` */ public echo(message: string): Promise { @@ -239,6 +291,13 @@ export class RedisClient extends BaseClient { * @returns - The current server time as a two items `array`: * A Unix timestamp and the amount of microseconds already elapsed in the current second. * The returned `array` is in a [Unix timestamp, Microseconds already elapsed] format. + * + * @example + * ```typescript + * // Example usage of time method without any argument + * const result = await client.time(); + * console.log(result); // Output: ['1710925775', '913580'] + * ``` */ public time(): Promise<[string, string]> { return this.createWritePromise(createTime()); diff --git a/node/src/RedisClusterClient.ts b/node/src/RedisClusterClient.ts index e19d00075f..517e3f4d74 100644 --- a/node/src/RedisClusterClient.ts +++ b/node/src/RedisClusterClient.ts @@ -262,9 +262,10 @@ export class RedisClusterClient extends BaseClient { * @remarks - This function should only be used for single-response commands. Commands that don't return response (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. * * @example - * Returns a list of all pub/sub clients on all primary nodes - * ```ts - * connection.customCommand(["CLIENT", "LIST","TYPE", "PUBSUB"], "allPrimaries") + * ```typescript + * // Example usage of customCommand method to retrieve pub/sub clients with routing to all primary nodes + * const result = await client.customCommand(["CLIENT", "LIST", "TYPE", "PUBSUB"], "allPrimaries"); + * console.log(result); // Output: Returns a list of all pub/sub clients * ``` */ public customCommand(args: string[], route?: Routes): Promise { @@ -303,6 +304,20 @@ export class RedisClusterClient extends BaseClient { * @param route - The command will be routed to all primaries, unless `route` is provided, in which * case the client will route the command to the nodes defined by `route`. * @returns - "PONG" if `message` is not provided, otherwise return a copy of `message`. + * + * @example + * ```typescript + * // Example usage of ping method without any message + * const result = await client.ping(); + * console.log(result); // Output: 'PONG' + * ``` + * + * @example + * ```typescript + * // Example usage of ping method with a message + * const result = await client.ping("Hello"); + * console.log(result); // Output: 'Hello' + * ``` */ public ping(message?: string, route?: Routes): Promise { return this.createWritePromise( @@ -340,6 +355,20 @@ export class RedisClusterClient extends BaseClient { * @returns - the name of the client connection as a string if a name is set, or null if no name is assigned. * When specifying a route other than a single node, it returns a dictionary where each address is the key and * its corresponding node response is the value. + * + * @example + * ```typescript + * // Example usage of client_getname method + * const result = await client.client_getname(); + * console.log(result); // Output: 'Connection Name' + * ``` + * + * @example + * ```typescript + * // Example usage of clientGetName method with routing to all nodes + * const result = await client.clientGetName('allNodes'); + * console.log(result); // Output: {'addr': 'Connection Name', 'addr2': 'Connection Name', 'addr3': 'Connection Name'} + * ``` */ public clientGetName( route?: Routes, @@ -357,6 +386,13 @@ export class RedisClusterClient extends BaseClient { * case the client will route the command to the nodes defined by `route`. * * @returns "OK" when the configuration was rewritten properly. Otherwise, an error is thrown. + * + * @example + * ```typescript + * // Example usage of configRewrite command + * const result = await client.configRewrite(); + * console.log(result); // Output: 'OK' + * ``` */ public configRewrite(route?: Routes): Promise<"OK"> { return this.createWritePromise( @@ -372,6 +408,13 @@ export class RedisClusterClient extends BaseClient { * case the client will route the command to the nodes defined by `route`. * * @returns always "OK". + * + * @example + * ```typescript + * // Example usage of configResetStat command + * const result = await client.configResetStat(); + * console.log(result); // Output: 'OK' + * ``` */ public configResetStat(route?: Routes): Promise<"OK"> { return this.createWritePromise( @@ -405,6 +448,20 @@ export class RedisClusterClient extends BaseClient { * * @returns A map of values corresponding to the configuration parameters. When specifying a route other than a single node, * it returns a dictionary where each address is the key and its corresponding node response is the value. + * + * @example + * ```typescript + * // Example usage of config_get method with a single configuration parameter with routing to a random node + * const result = await client.config_get(["timeout"], "randomNode"); + * console.log(result); // Output: {'timeout': '1000'} + * ``` + * + * @example + * ```typescript + * // Example usage of configGet method with multiple configuration parameters + * const result = await client.configGet(["timeout", "maxmemory"]); + * console.log(result); // Output: {'timeout': '1000', 'maxmemory': '1GB'} + * ``` */ public configGet( parameters: string[], @@ -427,8 +484,11 @@ export class RedisClusterClient extends BaseClient { * @returns "OK" when the configuration was set properly. Otherwise an error is thrown. * * @example - * config_set([("timeout", "1000")], [("maxmemory", "1GB")]) - Returns OK - * + * ```typescript + * // Example usage of configSet method to set multiple configuration parameters + * const result = await client.configSet({ timeout: "1000", maxmemory, "1GB" }); + * console.log(result); // Output: 'OK' + * ``` */ public configSet( parameters: Record, @@ -459,7 +519,7 @@ export class RedisClusterClient extends BaseClient { * ```typescript * // Example usage of the echo command with routing to all nodes * const echoedMessage = await client.echo("Glide-for-Redis", "allNodes"); - * console.log(echoedMessage); // Output: \{'addr': 'Glide-for-Redis', 'addr2': 'Glide-for-Redis', 'addr3': 'Glide-for-Redis'\} + * console.log(echoedMessage); // Output: {'addr': 'Glide-for-Redis', 'addr2': 'Glide-for-Redis', 'addr3': 'Glide-for-Redis'} * ``` */ public echo( @@ -483,6 +543,20 @@ export class RedisClusterClient extends BaseClient { * The returned `array` is in a [Unix timestamp, Microseconds already elapsed] format. * When specifying a route other than a single node, it returns a dictionary where each address is the key and * its corresponding node response is the value. + * + * @example + * ```typescript + * // Example usage of time method without any argument + * const result = await client.time(); + * console.log(result); // Output: ['1710925775', '913580'] + * ``` + * + * @example + * ```typescript + * // Example usage of time method with routing to all nodes + * const result = await client.time('allNodes'); + * console.log(result); // Output: {'addr': ['1710925775', '913580'], 'addr2': ['1710925775', '913580'], 'addr3': ['1710925775', '913580']} + * ``` */ public time(route?: Routes): Promise> { return this.createWritePromise(createTime(), toProtobufRoute(route)); diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 18de00aced..1bc305e16b 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -63,6 +63,7 @@ import { createSAdd, createSCard, createSMembers, + createSPop, createSRem, createSelect, createSet, @@ -678,6 +679,32 @@ export class BaseTransaction> { return this.addAndReturn(createSismember(key, member)); } + /** Removes and returns one random member from the set value store at `key`. + * See https://redis.io/commands/spop/ for details. + * To pop multiple members, see `spopCount`. + * + * @param key - The key of the set. + * + * Command Response - the value of the popped member. + * If `key` does not exist, null will be returned. + */ + public spop(key: string): T { + return this.addAndReturn(createSPop(key)); + } + + /** Removes and returns up to `count` random members from the set value store at `key`, depending on the set's length. + * See https://redis.io/commands/spop/ for details. + * + * @param key - The key of the set. + * @param count - The count of the elements to pop from the set. + * + * Command Response - A list of popped elements will be returned depending on the set's length. + * If `key` does not exist, empty list will be returned. + */ + public spopCount(key: string, count: number): T { + return this.addAndReturn(createSPop(key, count)); + } + /** Returns the number of keys in `keys` that exist in the database. * See https://redis.io/commands/exists/ for details. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 28290cfff6..670ca5ec98 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1122,6 +1122,29 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `spop and spopCount test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const members = ["member1", "member2", "member3"]; + expect(await client.sadd(key, members)).toEqual(3); + + const result1 = await client.spop(key); + expect(members).toContain(result1); + + const result2 = await client.spopCount(key, 2); + expect(members).toContain(result2?.[0]); + expect(members).toContain(result2?.[1]); + expect(result2).not.toContain(result1); + + expect(await client.spop("nonExistingKey")).toEqual(null); + expect(await client.spopCount("nonExistingKey", 1)).toEqual([]); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `exists with existing keys, an non existing key_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 435fb641a6..02b279b98a 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -144,6 +144,10 @@ export async function transactionTest( args.push(true); baseTransaction.smembers(key7); args.push(["bar"]); + baseTransaction.spop(key7); + args.push("bar"); + baseTransaction.scard(key7); + args.push(0); baseTransaction.zadd(key8, { member1: 1, member2: 2, diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index 74dcc0b470..23dff965f8 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -2764,7 +2764,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: base64:0.21.7 +Package: base64:0.22.0 The following copyrights and licenses were found in the source code of this package: @@ -3934,7 +3934,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: chrono:0.4.35 +Package: chrono:0.4.37 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9734,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.12 +Package: getrandom:0.2.13 The following copyrights and licenses were found in the source code of this package: @@ -13204,7 +13204,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libredox:0.0.1 +Package: libredox:0.1.3 The following copyrights and licenses were found in the source code of this package: @@ -13895,7 +13895,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: memchr:2.7.1 +Package: memchr:2.7.2 The following copyrights and licenses were found in the source code of this package: @@ -17273,7 +17273,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-lite:0.2.13 +Package: pin-project-lite:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -21272,7 +21272,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: redox_users:0.4.4 +Package: redox_users:0.4.5 The following copyrights and licenses were found in the source code of this package: @@ -22012,7 +22012,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.1 +Package: rustls-pemfile:2.1.2 The following copyrights and licenses were found in the source code of this package: @@ -22255,7 +22255,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.4.0 +Package: rustls-pki-types:1.4.1 The following copyrights and licenses were found in the source code of this package: @@ -22990,7 +22990,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.9.2 +Package: security-framework:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -23219,7 +23219,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.9.1 +Package: security-framework-sys:2.10.0 The following copyrights and licenses were found in the source code of this package: @@ -23962,693 +23962,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: signal-hook:0.3.17 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-registry:1.4.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook-tokio:0.3.1 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - Package: slab:0.4.9 The following copyrights and licenses were found in the source code of this package: @@ -25417,7 +24730,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.55 +Package: syn:2.0.58 The following copyrights and licenses were found in the source code of this package: @@ -27742,7 +27055,7 @@ the following restrictions: ---- -Package: tokio:1.36.0 +Package: tokio:1.37.0 The following copyrights and licenses were found in the source code of this package: @@ -40314,7 +39627,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: protobuf:5.26.0 +Package: protobuf:5.26.1 The following copyrights and licenses were found in the source code of this package: @@ -41402,7 +40715,7 @@ PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 ---- -Package: typing-extensions:4.10.0 +Package: typing-extensions:4.11.0 The following copyrights and licenses were found in the source code of this package: diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 1576a91466..e3727adadb 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -118,6 +118,10 @@ async def config_rewrite( Returns: OK: OK is returned when the configuration was rewritten properly. Otherwise an error is raised. + + Example: + >>> await client.config_rewrite() + 'OK' """ return cast( TOK, await self._execute_command(RequestType.ConfigRewrite, [], route) @@ -327,7 +331,7 @@ async def time(self, route: Optional[Route] = None) -> TClusterResponse[List[str Examples: >>> await client.time() ['1710925775', '913580'] - >>> await client.client_getname(AllNodes()) + >>> await client.time(AllNodes()) {'addr': ['1710925775', '913580'], 'addr2': ['1710925775', '913580'], 'addr3': ['1710925775', '913580']} """ return cast( diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 0d9ada155d..7bc02edf76 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -216,11 +216,7 @@ async def set( ) -> Optional[str]: """ Set the given key with the given value. Return value is dependent on the passed options. - See https://redis.io/commands/set/ for details. - - @example - Set "foo" to "bar" only if "foo" already exists, and set the key expiration to 5 seconds: - - connection.set("foo", "bar", conditional_set=ConditionalChange.ONLY_IF_EXISTS, expiry=Expiry(ExpiryType.SEC, 5)) + See https://redis.io/commands/set/ for more details. Args: key (str): the key to store. @@ -238,6 +234,16 @@ async def set( If the value is successfully set, return OK. If value isn't set because of only_if_exists or only_if_does_not_exist conditions, return None. If return_old_value is set, return the old value as a string. + + Example: + >>> await client.set("key", "value") + 'OK' + >>> await client.set("key", "new_value",conditional_set=ConditionalChange.ONLY_IF_EXISTS, expiry=Expiry(ExpiryType.SEC, 5)) + 'OK' # Set "new_value" to "key" only if "key" already exists, and set the key expiration to 5 seconds. + >>> await client.set("key", "value", conditional_set=ConditionalChange.ONLY_IF_DOES_NOT_EXIST,return_old_value=True) + 'new_value' # Returns the old value of "key". + >>> await client.get("key") + 'new_value' # Value wasn't modified back to being "value" because of "NX" flag. """ args = [key, value] if conditional_set: @@ -260,11 +266,34 @@ async def get(self, key: str) -> Optional[str]: Returns: Optional[str]: If the key exists, returns the value of the key as a string. Otherwise, return None. + + Example: + >>> await client.get("key") + 'value' """ return cast( Optional[str], await self._execute_command(RequestType.GetString, [key]) ) + async def strlen(self, key: str) -> int: + """ + Get the length of the string value stored at `key`. + See https://redis.io/commands/strlen/ for more details. + + Args: + key (str): The key to return its length. + + Returns: + int: The length of the string value stored at `key`. + If `key` does not exist, it is treated as an empty string and 0 is returned. + + Examples: + >>> await client.set("key", "GLIDE") + >>> await client.strlen("key") + 5 # Indicates that the length of the string value stored at `key` is 5. + """ + return cast(int, await self._execute_command(RequestType.Strlen, [key])) + async def delete(self, keys: List[str]) -> int: """ Delete one or more keys from the database. A key is ignored if it does not exist. @@ -275,6 +304,13 @@ async def delete(self, keys: List[str]) -> int: Returns: int: The number of keys that were deleted. + + Examples: + >>> await client.set("key", "value") + >>> await client.delete(["key"]) + 1 # Indicates that the key was successfully deleted. + >>> await client.delete(["key"]) + 0 # No keys we're deleted since "key" doesn't exist. """ return cast(int, await self._execute_command(RequestType.Del, keys)) @@ -289,6 +325,11 @@ async def incr(self, key: str) -> int: Returns: int: The value of `key` after the increment. + + Examples: + >>> await client.set("key", "10") + >>> await client.incr("key") + 11 """ return cast(int, await self._execute_command(RequestType.Incr, [key])) @@ -303,6 +344,11 @@ async def incrby(self, key: str, amount: int) -> int: Returns: int: The value of key after the increment. + + Example: + >>> await client.set("key", "10") + >>> await client.incrby("key" , 5) + 15 """ return cast( int, await self._execute_command(RequestType.IncrBy, [key, str(amount)]) @@ -321,6 +367,11 @@ async def incrbyfloat(self, key: str, amount: float) -> float: Returns: float: The value of key after the increment. + + Examples: + >>> await client.set("key", "10") + >>> await client.incrbyfloat("key" , 5.5) + 15.55 """ return cast( float, @@ -337,6 +388,10 @@ async def mset(self, key_value_map: Mapping[str, str]) -> TOK: Returns: OK: a simple OK response. + + Example: + >>> await client.mset({"key" : "value", "key2": "value2"}) + 'OK' """ parameters: List[str] = [] for pair in key_value_map.items(): @@ -354,6 +409,12 @@ async def mget(self, keys: List[str]) -> List[Optional[str]]: Returns: List[Optional[str]]: A list of values corresponding to the provided keys. If a key is not found, its corresponding value in the list will be None. + + Examples: + >>> await client.set("key1", "value1") + >>> await client.set("key2", "value2") + >>> await client.mget(["key1", "key2"]) + ['value1' , 'value2'] """ return cast( List[Optional[str]], await self._execute_command(RequestType.MGet, keys) @@ -370,6 +431,11 @@ async def decr(self, key: str) -> int: Returns: int: The value of key after the decrement. + + Examples: + >>> await client.set("key", "10") + >>> await client.decr("key") + 9 """ return cast(int, await self._execute_command(RequestType.Decr, [key])) @@ -385,6 +451,11 @@ async def decrby(self, key: str, amount: int) -> int: Returns: int: The value of key after the decrement. + + Example: + >>> await client.set("key", "10") + >>> await client.decrby("key" , 5) + 5 """ return cast( int, await self._execute_command(RequestType.DecrBy, [key, str(amount)]) @@ -405,7 +476,7 @@ async def hset(self, key: str, field_value_map: Mapping[str, str]) -> int: Example: >>> await client.hset("my_hash", {"field": "value", "field2": "value2"}) - 2 + 2 # Indicates that 2 fields were successfully set in the hash "my_hash". """ field_value_list: List[str] = [key] for pair in field_value_map.items(): @@ -429,6 +500,7 @@ async def hget(self, key: str, field: str) -> Optional[str]: Returns None if `field` is not presented in the hash or `key` does not exist. Examples: + >>> await client.hset("my_hash", "field") >>> await client.hget("my_hash", "field") "value" >>> await client.hget("my_hash", "nonexistent_field") @@ -646,12 +718,30 @@ async def hvals(self, key: str) -> List[str]: Returns: List[str]: A list of values in the hash, or an empty list when the key does not exist. - Examples: - >>> await client.hvals("my_hash") - ["value1", "value2", "value3"] # Returns all the values stored in the hash "my_hash". + Examples: + >>> await client.hvals("my_hash") + ["value1", "value2", "value3"] # Returns all the values stored in the hash "my_hash". """ return cast(List[str], await self._execute_command(RequestType.Hvals, [key])) + async def hkeys(self, key: str) -> List[str]: + """ + Returns all field names in the hash stored at `key`. + + See https://redis.io/commands/hkeys/ for more details. + + Args: + key (str): The key of the hash. + + Returns: + List[str]: A list of field names for the hash, or an empty list when the key does not exist. + + Examples: + >>> await client.hkeys("my_hash") + ["field1", "field2", "field3"] # Returns all the field names stored in the hash "my_hash". + """ + return cast(List[str], await self._execute_command(RequestType.Hkeys, [key])) + async def lpush(self, key: str, elements: List[str]) -> int: """ Insert all the specified values at the head of the list stored at `key`. @@ -667,8 +757,8 @@ async def lpush(self, key: str, elements: List[str]) -> int: int: The length of the list after the push operations. Examples: - >>> await client.lpush("my_list", ["value1", "value2"]) - 2 + >>> await client.lpush("my_list", ["value2", "value3"]) + 3 # Indicates that the new length of the list is 3 after the push operation. >>> await client.lpush("nonexistent_list", ["new_value"]) 1 """ @@ -714,9 +804,9 @@ async def lpop_count(self, key: str, count: int) -> Optional[List[str]]: If `key` does not exist, None will be returned. Examples: - >>> await client.lpop("my_list", 2) + >>> await client.lpop_count("my_list", 2) ["value1", "value2"] - >>> await client.lpop("non_exiting_key" , 3) + >>> await client.lpop_count("non_exiting_key" , 3) None """ return cast( @@ -806,8 +896,8 @@ async def rpush(self, key: str, elements: List[str]) -> int: int: The length of the list after the push operations. Examples: - >>> await client.rpush("my_list", ["value1", "value2"]) - 2 + >>> await client.rpush("my_list", ["value2", "value3"]) + 3 # Indicates that the new length of the list is 3 after the push operation. >>> await client.rpush("nonexistent_list", ["new_value"]) 1 """ @@ -853,9 +943,9 @@ async def rpop_count(self, key: str, count: int) -> Optional[List[str]]: If `key` does not exist, None will be returned. Examples: - >>> await client.rpop("my_list", 2) + >>> await client.rpop_count("my_list", 2) ["value1", "value2"] - >>> await client.rpop("non_exiting_key" , 7) + >>> await client.rpop_count("non_exiting_key" , 7) None """ return cast( @@ -1218,6 +1308,8 @@ async def ttl(self, key: str) -> int: 3600 # Indicates that "my_key" has a remaining time to live of 3600 seconds. >>> await client.ttl("nonexistent_key") -2 # Returns -2 for a non-existing key. + >>> await client.ttl("key") + -1 # Indicates that "key: has no has no associated expire. """ return cast(int, await self._execute_command(RequestType.TTL, [key])) @@ -1288,6 +1380,9 @@ async def type(self, key: str) -> str: >>> await client.set("key", "value") >>> await client.type("key") 'string' + >>> await client.lpush("key", ["value"]) + >>> await client.type("key") + 'list' """ return cast(str, await self._execute_command(RequestType.Type, [key])) @@ -1697,9 +1792,9 @@ async def zrem( If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. Examples: - >>> await zrem("my_sorted_set", ["member1", "member2"]) + >>> await client.zrem("my_sorted_set", ["member1", "member2"]) 2 # Indicates that two members have been removed from the sorted set "my_sorted_set." - >>> await zrem("non_existing_sorted_set", ["member1", "member2"]) + >>> await client.zrem("non_existing_sorted_set", ["member1", "member2"]) 0 # Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. """ return cast( @@ -1707,6 +1802,54 @@ async def zrem( await self._execute_command(RequestType.Zrem, [key] + members), ) + async def zremrangebyscore( + self, + key: str, + min_score: Union[InfBound, ScoreBoundary], + max_score: Union[InfBound, ScoreBoundary], + ) -> int: + """ + Removes all elements in the sorted set stored at `key` with a score between `min_score` and `max_score`. + + See https://redis.io/commands/zremrangebyscore/ for more details. + + Args: + key (str): The key of the sorted set. + min_score (Union[InfBound, ScoreBoundary]): The minimum score to remove from. + Can be an instance of InfBound representing positive/negative infinity, + or ScoreBoundary representing a specific score and inclusivity. + max_score (Union[InfBound, ScoreBoundary]): The maximum score to remove up to. + Can be an instance of InfBound representing positive/negative infinity, + or ScoreBoundary representing a specific score and inclusivity. + Returns: + int: The number of members that were removed from the sorted set. + If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + If `min_score` is greater than `max_score`, 0 is returned. + + Examples: + >>> await client.zremrangebyscore("my_sorted_set", ScoreBoundary(5.0 , is_inclusive=true) , InfBound.POS_INF) + 2 # Indicates that 2 members with scores between 5.0 (not exclusive) and +inf have been removed from the sorted set "my_sorted_set". + >>> await client.zremrangebyscore("non_existing_sorted_set", ScoreBoundary(5.0 , is_inclusive=true) , ScoreBoundary(10.0 , is_inclusive=false)) + 0 # Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. + """ + score_min = ( + min_score.value["score_arg"] + if type(min_score) == InfBound + else min_score.value + ) + score_max = ( + max_score.value["score_arg"] + if type(max_score) == InfBound + else max_score.value + ) + + return cast( + int, + await self._execute_command( + RequestType.ZRemRangeByScore, [key, score_min, score_max] + ), + ) + async def zscore(self, key: str, member: str) -> Optional[float]: """ Returns the score of `member` in the sorted set stored at `key`. @@ -1723,9 +1866,9 @@ async def zscore(self, key: str, member: str) -> Optional[float]: If `key` does not exist, None is returned. Examples: - >>> await zscore("my_sorted_set", "member") + >>> await client.zscore("my_sorted_set", "member") 10.5 # Indicates that the score of "member" in the sorted set "my_sorted_set" is 10.5. - >>> await zscore("my_sorted_set", "non_existing_member") + >>> await client.zscore("my_sorted_set", "non_existing_member") None """ return cast( diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index f84cc395e4..233c584c0c 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -114,6 +114,20 @@ def set( args.extend(expiry.get_cmd_args()) return self.append_command(RequestType.SetString, args) + def strlen(self: TTransaction, key: str) -> TTransaction: + """ + Get the length of the string value stored at `key`. + See https://redis.io/commands/strlen/ for more details. + + Args: + key (str): The key to return its length. + + Commands response: + int: The length of the string value stored at `key`. + If `key` does not exist, it is treated as an empty string and 0 is returned. + """ + return self.append_command(RequestType.Strlen, [key]) + def custom_command(self: TTransaction, command_args: List[str]) -> TTransaction: """ Executes a single command, without checking inputs. @@ -543,6 +557,20 @@ def hvals(self: TTransaction, key: str) -> TTransaction: """ return self.append_command(RequestType.Hvals, [key]) + def hkeys(self: TTransaction, key: str) -> TTransaction: + """ + Returns all field names in the hash stored at `key`. + + See https://redis.io/commands/hkeys/ for more details. + + Args: + key (str): The key of the hash. + + Command response: + List[str]: A list of field names for the hash, or an empty list when the key does not exist. + """ + return self.append_command(RequestType.Hkeys, [key]) + def lpush(self: TTransaction, key: str, elements: List[str]) -> TTransaction: """ Insert all the specified values at the head of the list stored at `key`. @@ -1361,6 +1389,45 @@ def zrem( """ return self.append_command(RequestType.Zrem, [key] + members) + def zremrangebyscore( + self: TTransaction, + key: str, + min_score: Union[InfBound, ScoreBoundary], + max_score: Union[InfBound, ScoreBoundary], + ) -> TTransaction: + """ + Removes all elements in the sorted set stored at `key` with a score between `min_score` and `max_score`. + + See https://redis.io/commands/zremrangebyscore/ for more details. + + Args: + key (str): The key of the sorted set. + min_score (Union[InfBound, ScoreBoundary]): The minimum score to remove from. + Can be an instance of InfBound representing positive/negative infinity, + or ScoreBoundary representing a specific score and inclusivity. + max_score (Union[InfBound, ScoreBoundary]): The maximum score to remove up to. + Can be an instance of InfBound representing positive/negative infinity, + or ScoreBoundary representing a specific score and inclusivity. + + Commands response: + int: The number of members that were removed from the sorted set. + If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. + If `min_score` is greater than `max_score`, 0 is returned. + """ + score_min = ( + min_score.value["score_arg"] + if type(min_score) == InfBound + else min_score.value + ) + score_max = ( + max_score.value["score_arg"] + if type(max_score) == InfBound + else max_score.value + ) + return self.append_command( + RequestType.ZRemRangeByScore, [key, score_min, score_max] + ) + def zscore(self: TTransaction, key: str, member: str) -> TTransaction: """ Returns the score of `member` in the sorted set stored at `key`. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 43259ac0d9..7d5131b201 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -30,7 +30,7 @@ ScoreBoundary, ) from glide.config import ProtocolVersion, RedisCredentials -from glide.constants import OK +from glide.constants import OK, TResult from glide.redis_client import RedisClient, RedisClusterClient, TRedisClient from glide.routes import ( AllNodes, @@ -776,6 +776,25 @@ async def test_hvals(self, redis_client: TRedisClient): with pytest.raises(RequestError): await redis_client.hvals(key2) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_hkeys(self, redis_client: TRedisClient): + key = get_random_string(10) + key2 = get_random_string(5) + field = get_random_string(5) + field2 = get_random_string(5) + field_value_map = {field: "value", field2: "value2"} + + assert await redis_client.hset(key, field_value_map) == 2 + assert await redis_client.hkeys(key) == [field, field2] + assert await redis_client.hdel(key, [field]) == 1 + assert await redis_client.hkeys(key) == [field2] + assert await redis_client.hkeys("non_existing_key") == [] + + assert await redis_client.set(key2, "value") == OK + with pytest.raises(RequestError): + await redis_client.hkeys(key2) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_lpush_lpop_lrange(self, redis_client: TRedisClient): @@ -960,6 +979,21 @@ async def test_llen(self, redis_client: TRedisClient): await redis_client.llen(key2) assert "Operation against a key holding the wrong kind of value" in str(e) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_strlen(self, redis_client: TRedisClient): + key1 = get_random_string(10) + key2 = get_random_string(10) + value_list = ["value4", "value3", "value2", "value1"] + + assert await redis_client.set(key1, "foo") == OK + assert await redis_client.strlen(key1) == 3 + assert await redis_client.strlen("non_existing_key") == 0 + + assert await redis_client.lpush(key2, value_list) == 4 + with pytest.raises(RequestError): + assert await redis_client.strlen(key2) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_exists(self, redis_client: TRedisClient): @@ -1215,6 +1249,34 @@ async def test_zrem(self, redis_client: TRedisClient): assert await redis_client.zrem("non_existing_set", ["member"]) == 0 + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zremrangebyscore(self, redis_client: TRedisClient): + key = get_random_string(10) + members_scores = {"one": 1, "two": 2, "three": 3} + assert await redis_client.zadd(key, members_scores) == 3 + + assert ( + await redis_client.zremrangebyscore( + key, ScoreBoundary(1, False), ScoreBoundary(2) + ) + == 1 + ) + assert ( + await redis_client.zremrangebyscore(key, ScoreBoundary(1), InfBound.NEG_INF) + == 0 + ) + assert ( + await redis_client.zremrangebyscore( + "non_existing_set", InfBound.NEG_INF, InfBound.POS_INF + ) + == 0 + ) + + assert await redis_client.set(key, "value") == OK + with pytest.raises(RequestError): + await redis_client.zremrangebyscore(key, InfBound.NEG_INF, InfBound.POS_INF) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_zcard(self, redis_client: TRedisClient): @@ -1760,9 +1822,14 @@ async def test_cluster_route_by_address_reaches_correct_node( self, redis_client: RedisClusterClient ): # returns the line that contains the word "myself", up to that point. This is done because the values after it might change with time. - clean_result = lambda value: [ - line for line in value.split("\n") if "myself" in line - ][0] + def clean_result(value: TResult): + assert type(value) is str + for line in value.splitlines(): + if "myself" in line: + return line.split("myself")[0] + raise Exception( + f"Couldn't find 'myself' in the cluster nodes output: {value}" + ) cluster_nodes = clean_result( await redis_client.custom_command(["cluster", "nodes"], RandomNode()) diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index d623b27fff..2828bdd620 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -47,6 +47,8 @@ async def transaction_test( args.append("string") transaction.echo(value) args.append(value) + transaction.strlen(key) + args.append(len(value)) transaction.persist(key) args.append(False) @@ -96,6 +98,8 @@ async def transaction_test( args.append(2) transaction.hvals(key4) args.append([value, value2]) + transaction.hkeys(key4) + args.append([key, key2]) transaction.hsetnx(key4, key, value) args.append(False) transaction.hincrby(key4, key3, 5) @@ -150,8 +154,8 @@ async def transaction_test( transaction.sismember(key7, "bar") args.append(True) - transaction.zadd(key8, {"one": 1, "two": 2, "three": 3}) - args.append(3) + transaction.zadd(key8, {"one": 1, "two": 2, "three": 3, "four": 4}) + args.append(4) transaction.zrank(key8, "one") args.append(0) if not await check_if_server_version_lt(redis_client, "7.2.0"): @@ -162,19 +166,21 @@ async def transaction_test( transaction.zrem(key8, ["one"]) args.append(1) transaction.zcard(key8) - args.append(2) + args.append(3) transaction.zcount(key8, ScoreBoundary(2, is_inclusive=True), InfBound.POS_INF) - args.append(2) + args.append(3) transaction.zscore(key8, "two") args.append(2.0) transaction.zrange(key8, RangeByIndex(start=0, stop=-1)) - args.append(["two", "three"]) + args.append(["two", "three", "four"]) transaction.zrange_withscores(key8, RangeByIndex(start=0, stop=-1)) - args.append({"two": 2, "three": 3}) + args.append({"two": 2, "three": 3, "four": 4}) transaction.zpopmin(key8) args.append({"two": 2.0}) transaction.zpopmax(key8) - args.append({"three": 3}) + args.append({"four": 4}) + transaction.zremrangebyscore(key8, InfBound.NEG_INF, InfBound.POS_INF) + args.append(1) return args