From 1efc8d4983f1ee8218ef8f995287d205b3a402a7 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Wed, 3 Apr 2024 13:11:44 -0700 Subject: [PATCH 01/22] Javadoc fixes. (#1185) * Javadoc fixes. --------- Signed-off-by: Yury-Fridlyand --- .../src/main/java/glide/api/BaseClient.java | 30 ++--- .../ConnectionManagementClusterCommands.java | 10 +- .../ConnectionManagementCommands.java | 4 +- .../api/commands/GenericBaseCommands.java | 6 +- .../api/commands/GenericClusterCommands.java | 8 +- .../glide/api/commands/GenericCommands.java | 17 +-- .../glide/api/commands/HashBaseCommands.java | 6 +- .../glide/api/commands/ListBaseCommands.java | 59 +++++---- .../ServerManagementClusterCommands.java | 10 +- .../commands/ServerManagementCommands.java | 5 +- .../glide/api/commands/SetBaseCommands.java | 12 +- .../api/commands/SortedSetBaseCommands.java | 29 +++-- .../glide/api/commands/StringCommands.java | 54 ++++---- .../glide/api/models/BaseTransaction.java | 123 +++++++++--------- .../java/glide/api/models/ClusterValue.java | 2 +- .../java/glide/api/models/Transaction.java | 18 +-- .../glide/api/models/commands/SetOptions.java | 6 +- 17 files changed, 202 insertions(+), 197 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index fdc99cc981..d3855ba77f 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -108,10 +108,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 +169,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) @@ -237,8 +237,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 { 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..32b1e85af1 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java @@ -13,7 +13,8 @@ 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..302b89e106 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java @@ -11,7 +11,7 @@ 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..e0dda2280f 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -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..083d51fd67 100644
--- a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
+++ b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java
@@ -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..fa2e481cec 100644 --- a/java/client/src/main/java/glide/api/commands/GenericCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericCommands.java @@ -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..1ffc19a5fe 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -59,7 +59,7 @@ 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.
      * }
*/ @@ -142,7 +142,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 +154,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/ListBaseCommands.java b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java
index 64ead64e59..4e175e5b4d 100644
--- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java
@@ -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();
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..6355ecf788 100644
--- a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java
+++ b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java
@@ -17,8 +17,8 @@
 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..994e347853 100644
--- a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java
+++ b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java
@@ -14,7 +14,8 @@
 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..beea7bb9b2 100644
--- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java
@@ -12,8 +12,8 @@
  */
 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.
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..3358555882 100644
--- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
@@ -23,14 +23,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 +50,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 +71,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 +101,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 +112,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 +131,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. * diff --git a/java/client/src/main/java/glide/api/commands/StringCommands.java b/java/client/src/main/java/glide/api/commands/StringCommands.java index 99295aae08..592a35a1bf 100644 --- a/java/client/src/main/java/glide/api/commands/StringCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringCommands.java @@ -16,8 +16,8 @@ public interface StringCommands { /** - * 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..3287678f95 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -101,18 +101,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 +134,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 +145,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 +159,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 +171,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 +201,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 +216,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 +231,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 +252,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 +269,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 +284,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 +299,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 +315,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 +333,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 +348,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 +379,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); @@ -506,7 +504,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 +516,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 +550,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 +569,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 +580,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 +595,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 +605,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 +635,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 +653,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 +694,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 +711,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 +721,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 +740,7 @@ public T sadd(@NonNull String key, @NonNull 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. @@ -750,7 +749,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 +759,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 +774,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 +836,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 +1040,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 +1072,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 +1083,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 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/SetOptions.java b/java/client/src/main/java/glide/api/models/commands/SetOptions.java index 05f4cd5bec..a875f845f9 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 @@ -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; From 34341ef0bf6a82fe71eab69e205e684e1ba52f26 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:53:41 -0700 Subject: [PATCH 02/22] Java: Add Zrange and ZrangeWithScores command. (#1166) * Java: Add Zrange and ZrangeWithScores command. --- .../src/main/java/glide/api/BaseClient.java | 34 ++ .../api/commands/SortedSetBaseCommands.java | 127 +++++++ .../glide/api/models/BaseTransaction.java | 102 ++++++ .../api/models/commands/RangeOptions.java | 324 ++++++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 155 +++++++++ .../glide/api/models/TransactionTests.java | 44 +++ .../test/java/glide/SharedCommandTests.java | 134 ++++++++ .../java/glide/TransactionTestUtilities.java | 5 + 8 files changed, 925 insertions(+) create mode 100644 java/client/src/main/java/glide/api/models/commands/RangeOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index d3855ba77f..c1054a97be 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -52,6 +52,7 @@ 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.Zrem; import glide.api.commands.GenericBaseCommands; @@ -62,6 +63,9 @@ import glide.api.commands.StringCommands; 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.ZaddOptions; @@ -658,4 +662,34 @@ public CompletableFuture persist(@NonNull String key) { public CompletableFuture type(@NonNull String key) { return commandManager.submitNewCommand(Type, new String[] {key}, this::handleStringResponse); } + + @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); + } } 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 3358555882..a73267f5c1 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -1,6 +1,11 @@ /** 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; @@ -12,6 +17,7 @@ * @see Sorted Set Commands */ public interface SortedSetBaseCommands { + public static final String WITH_SCORES_REDIS_API = "WITHSCORES"; /** * Adds members with their scores to the sorted set stored at key.
@@ -284,4 +290,125 @@ 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); } 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 3287678f95..8c8757d2a4 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -1,6 +1,7 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models; +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; @@ -61,11 +62,17 @@ 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.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; @@ -1424,6 +1431,101 @@ public T type(@NonNull String key) { 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); + } + /** 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/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/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index a17615d1ac..14100f0b09 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -2,6 +2,7 @@ package glide.api; import static glide.api.BaseClient.OK; +import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_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; @@ -73,11 +74,20 @@ 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.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; @@ -1914,6 +1924,151 @@ 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 type_returns_success() { 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..160630648d 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -1,6 +1,7 @@ /** 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.models.commands.SetOptions.RETURN_OLD_VALUE; import static org.junit.jupiter.api.Assertions.assertEquals; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; @@ -59,10 +60,15 @@ 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.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.ZaddOptions; import java.util.LinkedHashMap; @@ -414,6 +420,44 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.type("key"); results.add(Pair.of(Type, ArgsArray.newBuilder().addArgs("key").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())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a7ddbe0dc9..2676be7d95 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -21,6 +21,14 @@ 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.ZaddOptions; @@ -1206,4 +1214,130 @@ 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 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); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 5af67f20b5..64b5331d98 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -4,6 +4,7 @@ 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 java.util.Map; import java.util.Set; @@ -88,6 +89,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact 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); @@ -150,6 +153,8 @@ public static Object[] transactionTestResult() { 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) From 6b02d16baea4a8df0df5f005246f22a1a21e1ebd Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:54:41 -0700 Subject: [PATCH 03/22] Java: Add Zrank and Zrankwithscores command. (Sorted Set Commands) (#1179) * Java: Add Zrank and Zrankwithscores command. (Sorted Set Commands) (#154) --- .../src/main/java/glide/api/BaseClient.java | 17 ++++++ .../api/commands/SortedSetBaseCommands.java | 45 ++++++++++++++++ .../glide/api/models/BaseTransaction.java | 38 ++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 52 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 15 ++++++ .../test/java/glide/SharedCommandTests.java | 24 +++++++++ .../java/glide/TransactionTestUtilities.java | 2 + 7 files changed, 193 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index c1054a97be..5587bf150b 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -53,6 +53,7 @@ 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; @@ -223,6 +224,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); } @@ -647,6 +652,18 @@ 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 pttl(@NonNull String key) { return commandManager.submitNewCommand(PTTL, new String[] {key}, this::handleLongResponse); 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 a73267f5c1..462a7305c4 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -18,6 +18,7 @@ */ 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.
@@ -411,4 +412,48 @@ CompletableFuture> zrangeWithScores( * }
*/ 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/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 8c8757d2a4..1decc9f3b5 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -1,6 +1,7 @@ /** 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; @@ -63,6 +64,7 @@ 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; @@ -1372,6 +1374,42 @@ 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(); + } + /** * Returns the remaining time to live of key that has a timeout, in milliseconds. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 14100f0b09..8901c73c1a 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -3,6 +3,7 @@ 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; @@ -75,6 +76,7 @@ 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; @@ -2069,6 +2071,56 @@ public void zrangeWithScores_by_score_returns_success() { 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 type_returns_success() { 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 160630648d..9d4e36eb4c 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -2,6 +2,7 @@ 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.ClientGetName; @@ -61,6 +62,7 @@ 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; @@ -411,6 +413,19 @@ 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.time(); results.add(Pair.of(Time, ArgsArray.newBuilder().build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 2676be7d95..49c32b28da 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1179,6 +1179,30 @@ 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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 64b5331d98..95cf3caa06 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -86,6 +86,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact 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); @@ -150,6 +151,7 @@ public static Object[] transactionTestResult() { 1L, Set.of("baz"), 3L, + 0L, // zrank(key8, "one") 4.0, 1L, 2L, From 5f0b8a39cfbb9eeef8acebcaf45068c7817452da Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:33:40 -0700 Subject: [PATCH 04/22] Java: Add hvals command. (Hash Command Group) (#1210) Java: Add hvals command. (Hash Command Group) (#164) --- .../src/main/java/glide/api/BaseClient.java | 9 +++++++ .../glide/api/commands/HashBaseCommands.java | 15 +++++++++++ .../glide/api/models/BaseTransaction.java | 16 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 25 +++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 +++ .../test/java/glide/SharedCommandTests.java | 27 +++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 98 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 5587bf150b..f569c6ad3c 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -21,6 +21,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; @@ -347,6 +348,14 @@ public CompletableFuture hdel(@NonNull String key, @NonNull String[] field return commandManager.submitNewCommand(HashDel, args, 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); 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 1ffc19a5fe..f267d7902c 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -65,6 +65,21 @@ public interface HashBaseCommands { */ CompletableFuture hdel(String key, String[] fields); + /** + * 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. * 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 1decc9f3b5..7e505f80f0 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -29,6 +29,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; @@ -438,6 +439,21 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { 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. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8901c73c1a..8f5bf6f5e6 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -40,6 +40,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; @@ -1021,6 +1022,30 @@ public void hdel_success() { 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() { 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 9d4e36eb4c..bab5d5fa6f 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -27,6 +27,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; @@ -169,6 +170,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hdel("key", new String[] {"field"}); results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 49c32b28da..2005d4138b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -37,6 +37,7 @@ 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; @@ -471,6 +472,32 @@ 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 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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 95cf3caa06..1ddd89e910 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -64,6 +64,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact 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); @@ -134,6 +135,7 @@ public static Object[] transactionTestResult() { new String[] {value1, null, value2}, Map.of(field1, value1, field2, value2), 1L, + new String[] {value2}, // hvals(key4) 5L, 10.5, 5L, From a422b2428693ec9b139e60f4a76f4a2a71c28926 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Wed, 3 Apr 2024 16:03:46 -0700 Subject: [PATCH 05/22] Java: Add hsetnx command. (Hash Command Group) (#162) (#1211) * Java: Add hsetnx command. (Hash Command Group) (#162) * Minor documentation update. * Convert booleans to Boolean and fix example Signed-off-by: Andrew Carbonetto --------- Signed-off-by: Andrew Carbonetto Co-authored-by: Andrew Carbonetto --- .../src/main/java/glide/api/BaseClient.java | 8 +++++ .../glide/api/commands/HashBaseCommands.java | 23 +++++++++++++++ .../glide/api/models/BaseTransaction.java | 21 ++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 29 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 7 +++++ .../test/java/glide/SharedCommandTests.java | 19 ++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 109 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f569c6ad3c..9a7330ae71 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -13,6 +13,7 @@ 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.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -342,6 +343,13 @@ 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); 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 f267d7902c..5f1481fed9 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -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. 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 7e505f80f0..d02cc16420 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -21,6 +21,7 @@ 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.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -421,6 +422,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. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8f5bf6f5e6..397bda7e56 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -13,6 +13,7 @@ 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; @@ -32,6 +33,7 @@ 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.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -999,6 +1001,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() { @@ -1010,6 +1037,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); 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 bab5d5fa6f..123d8ed0eb 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -19,6 +19,7 @@ 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.HSetNX; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; @@ -164,6 +165,12 @@ 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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 2005d4138b..f296e32c09 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -455,6 +455,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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 1ddd89e910..67fa5f0422 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -61,6 +61,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); 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}); @@ -132,6 +133,7 @@ public static Object[] transactionTestResult() { 2L, value1, true, + Boolean.FALSE, // hsetnx(key4, field1, value1) new String[] {value1, null, value2}, Map.of(field1, value1, field2, value2), 1L, From 0e0b932c8ce81c55e4e130d0474f3569dd5f00a3 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:09:51 -0700 Subject: [PATCH 06/22] Python: adds HKEYS command (#1228) --- CHANGELOG.md | 3 ++- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + python/python/glide/async_commands/core.py | 24 ++++++++++++++++--- .../glide/async_commands/transaction.py | 14 +++++++++++ python/python/tests/test_async_client.py | 19 +++++++++++++++ python/python/tests/test_transaction.py | 2 ++ 7 files changed, 60 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e7b936a1..b09023f9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ #### Changes * Python: Added JSON.DEL JSON.FORGET commands ([#1146](https://github.com/aws/glide-for-redis/pull/1146)) -* +* Python: Added HKEYS command ([#1228](https://github.com/aws/glide-for-redis/pull/1228)) + #### Fixes * Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203)) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 327b9fa335..33e4fcea77 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -135,6 +135,7 @@ enum RequestType { Rename = 91; DBSize = 92; Brpop = 93; + Hkeys = 94; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 10d1935200..2eabf6e80a 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -362,6 +362,7 @@ fn get_command(request: &Command) -> Option { RequestType::Rename => Some(cmd("RENAME")), RequestType::DBSize => Some(cmd("DBSIZE")), RequestType::Brpop => Some(cmd("BRPOP")), + RequestType::Hkeys => Some(cmd("HKEYS")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 0d9ada155d..d78f86bf5a 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -646,12 +646,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`. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index f84cc395e4..cce299dadc 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -543,6 +543,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`. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 43259ac0d9..0e9aeb3132 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -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): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index d623b27fff..543d3c6e64 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -96,6 +96,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) From 60b596c9665f9458cfb0983166f351f2644457d6 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:43:58 -0700 Subject: [PATCH 07/22] Java: Add hlen command. (Hash Command Group) (#1212) * Java: Add hlen command. (Hash Command Group) (#159) --- .../src/main/java/glide/api/BaseClient.java | 6 +++++ .../glide/api/commands/HashBaseCommands.java | 18 ++++++++++++++ .../glide/api/models/BaseTransaction.java | 17 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 24 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 ++++ .../test/java/glide/SharedCommandTests.java | 24 +++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 95 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 9a7330ae71..dc7c4e754c 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -13,6 +13,7 @@ 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; @@ -356,6 +357,11 @@ public CompletableFuture hdel(@NonNull String key, @NonNull String[] field 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( 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 5f1481fed9..b76dd96a2c 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -88,6 +88,24 @@ public interface HashBaseCommands { */ 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. * 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 d02cc16420..0e84bd6007 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -21,6 +21,7 @@ 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; @@ -460,6 +461,22 @@ 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. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 397bda7e56..7191ebed0b 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -33,6 +33,7 @@ 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; @@ -1051,6 +1052,29 @@ 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() { 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 123d8ed0eb..2beaf13e10 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -19,6 +19,7 @@ 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; @@ -177,6 +178,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) 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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index f296e32c09..a9c76ba3d7 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -491,6 +491,30 @@ 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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 67fa5f0422..76442dfdab 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -60,6 +60,7 @@ 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}); @@ -132,6 +133,7 @@ public static Object[] transactionTestResult() { 1L, 2L, value1, + 2L, // hlen(key4) true, Boolean.FALSE, // hsetnx(key4, field1, value1) new String[] {value1, null, value2}, From cd8c265a3c4365abf4d77d870c238a98dd86f39d Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 09:45:40 -0700 Subject: [PATCH 08/22] Groom interface docs and names. (#1214) * Groom interface docs and names. --------- Signed-off-by: Yury-Fridlyand --- java/client/src/main/java/glide/api/BaseClient.java | 4 ++-- .../api/commands/ConnectionManagementClusterCommands.java | 2 +- .../glide/api/commands/ConnectionManagementCommands.java | 2 +- .../main/java/glide/api/commands/GenericBaseCommands.java | 4 ++-- .../java/glide/api/commands/GenericClusterCommands.java | 2 +- .../src/main/java/glide/api/commands/GenericCommands.java | 2 +- .../src/main/java/glide/api/commands/HashBaseCommands.java | 4 ++-- .../src/main/java/glide/api/commands/ListBaseCommands.java | 4 ++-- .../glide/api/commands/ServerManagementClusterCommands.java | 6 +++--- .../java/glide/api/commands/ServerManagementCommands.java | 2 +- .../src/main/java/glide/api/commands/SetBaseCommands.java | 4 ++-- .../main/java/glide/api/commands/SortedSetBaseCommands.java | 4 ++-- .../{StringCommands.java => StringBaseCommands.java} | 6 +++--- .../src/main/java/glide/api/models/commands/SetOptions.java | 4 ++-- 14 files changed, 25 insertions(+), 25 deletions(-) rename java/client/src/main/java/glide/api/commands/{StringCommands.java => StringBaseCommands.java} (98%) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index dc7c4e754c..ef6f87f530 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -64,7 +64,7 @@ import glide.api.commands.ListBaseCommands; import glide.api.commands.SetBaseCommands; import glide.api.commands.SortedSetBaseCommands; -import glide.api.commands.StringCommands; +import glide.api.commands.StringBaseCommands; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.RangeOptions; @@ -101,7 +101,7 @@ public abstract class BaseClient implements AutoCloseable, GenericBaseCommands, - StringCommands, + StringBaseCommands, HashBaseCommands, ListBaseCommands, SetBaseCommands, 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 32b1e85af1..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,7 +6,7 @@ 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 */ 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 302b89e106..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,7 +4,7 @@ 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 */ 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 e0dda2280f..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 */ 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 083d51fd67..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 */ 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 fa2e481cec..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 */ 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 b76dd96a2c..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 */ 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 4e175e5b4d..fcc0842dc1 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 */ 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 6355ecf788..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,10 +9,10 @@ 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 { 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 994e347853..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,7 +7,7 @@ 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 */ 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 beea7bb9b2..16573f883c 100644 --- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java @@ -5,8 +5,8 @@ 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 */ 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 462a7305c4..d4a9e5d4ea 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -11,8 +11,8 @@ 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 */ 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 98% 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 592a35a1bf..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,12 +8,12 @@ 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 { /** * Gets the value associated with the given key, or null if no such 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 a875f845f9..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 */ From c37238ecb3cafc99806ae03c33faaad1283e5036 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 10:51:46 -0700 Subject: [PATCH 09/22] Java: Add `PFADD` command. (#1221) * Add `PFADD` command. (#166) Signed-off-by: Yury-Fridlyand --- CHANGELOG.md | 2 +- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + .../src/main/java/glide/api/BaseClient.java | 11 +++++- .../api/commands/HyperLogLogBaseCommands.java | 38 +++++++++++++++++++ .../glide/api/models/BaseTransaction.java | 23 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 26 +++++++++++++ .../glide/api/models/TransactionTests.java | 7 ++++ .../test/java/glide/SharedCommandTests.java | 17 +++++++++ .../java/glide/TransactionTestUtilities.java | 4 ++ 10 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b09023f9f4..8be4873cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,4 +83,4 @@ Preview release of **GLIDE for Redis** a Polyglot Redis client. -See the [README](README.md) for additional information. \ No newline at end of file +See the [README](README.md) for additional information. diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 33e4fcea77..9c76d9e887 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -136,6 +136,7 @@ enum RequestType { DBSize = 92; Brpop = 93; Hkeys = 94; + PfAdd = 96; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 2eabf6e80a..648b9e4d40 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -363,6 +363,7 @@ fn get_command(request: &Command) -> Option { RequestType::DBSize => Some(cmd("DBSIZE")), RequestType::Brpop => Some(cmd("BRPOP")), RequestType::Hkeys => Some(cmd("HKEYS")), + RequestType::PfAdd => Some(cmd("PFADD")), } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index ef6f87f530..1b577e0440 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -39,6 +39,7 @@ 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.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; @@ -61,6 +62,7 @@ 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; @@ -105,7 +107,8 @@ public abstract class BaseClient HashBaseCommands, ListBaseCommands, SetBaseCommands, - SortedSetBaseCommands { + SortedSetBaseCommands, + HyperLogLogBaseCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -732,4 +735,10 @@ 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); + } } 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..258f6c4c62 --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java @@ -0,0 +1,38 @@ +/** 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); +} 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 0e84bd6007..6f6c2b0dd5 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -48,6 +48,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -1618,6 +1619,28 @@ public T zrangeWithScores(@NonNull String key, @NonNull ScoredRangeQuery rangeQu 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(); + } + /** 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/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 7191ebed0b..eca029a1d5 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -60,6 +60,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -2242,4 +2243,29 @@ public void time_returns_success() { assertEquals(testResponse, response); assertEquals(payload, response.get()); } + + @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(payload, 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 2beaf13e10..3a2a996192 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -46,6 +46,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -488,6 +489,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 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())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a9c76ba3d7..87c334a98d 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1434,4 +1434,21 @@ public void zrange_with_different_types_of_keys(BaseClient client) { 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); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 76442dfdab..4cd64cf327 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -19,6 +19,7 @@ public class TransactionTestUtilities { private static final String key6 = "{key}" + UUID.randomUUID(); private static final String key7 = "{key}" + UUID.randomUUID(); private static final String key8 = "{key}" + UUID.randomUUID(); + private static final String hllKey1 = "{key}:hllKey1-" + 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(); @@ -106,6 +107,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.echo("GLIDE"); + baseTransaction.pfadd(hllKey1, new String[] {"a", "b", "c"}); + return baseTransaction; } @@ -170,6 +173,7 @@ public static Object[] transactionTestResult() { Map.of("timeout", "1000"), OK, "GLIDE", // echo + 1L, // pfadd(hllKey1, new String[] {"a", "b", "c"}) }; } } From 6ef09f9a18fe61aa59072a38051184f80b717d1d Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 12:38:08 -0700 Subject: [PATCH 10/22] Java: Update timeout handling in IT. (#1217) Update timeout handling in IT. (#170) Signed-off-by: Yury-Fridlyand --- .../src/test/java/glide/ConnectionTests.java | 7 ++++--- .../src/test/java/glide/ErrorHandlingTests.java | 16 +++++++--------- .../src/test/java/glide/SharedClientTests.java | 2 +- .../src/test/java/glide/SharedCommandTests.java | 2 +- .../java/glide/cluster/ClusterClientTests.java | 2 +- .../glide/cluster/ClusterTransactionTests.java | 13 ++++++------- .../test/java/glide/cluster/CommandTests.java | 6 ++---- .../test/java/glide/standalone/CommandTests.java | 2 +- .../glide/standalone/StandaloneClientTests.java | 2 +- .../java/glide/standalone/TransactionTests.java | 13 +++++++------ 10 files changed, 31 insertions(+), 34 deletions(-) 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 87c334a98d..5a2c984ea5 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -53,7 +53,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; 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); } } From dde7760d57c06690b132e1b5cf2264aab43e3de9 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 4 Apr 2024 13:51:41 -0700 Subject: [PATCH 11/22] Java: Add XADD command (Stream commands) (#1209) * Java: Add XADD command (Stream commands) (#155) Signed-off-by: Andrew Carbonetto Co-authored-by: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> --- .../src/main/java/glide/api/BaseClient.java | 18 ++ .../api/commands/StreamBaseCommands.java | 51 +++++ .../glide/api/models/BaseTransaction.java | 36 ++++ .../api/models/commands/StreamAddOptions.java | 177 +++++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 183 ++++++++++++++++++ .../glide/api/models/TransactionTests.java | 24 +++ .../test/java/glide/SharedCommandTests.java | 104 +++++++++- .../java/glide/TransactionTestUtilities.java | 15 ++ 8 files changed, 598 insertions(+), 10 deletions(-) create mode 100644 java/client/src/main/java/glide/api/commands/StreamBaseCommands.java create mode 100644 java/client/src/main/java/glide/api/models/commands/StreamAddOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 1b577e0440..1bba47390d 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -51,6 +51,7 @@ 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; @@ -66,6 +67,7 @@ import glide.api.commands.ListBaseCommands; import glide.api.commands.SetBaseCommands; import glide.api.commands.SortedSetBaseCommands; +import glide.api.commands.StreamBaseCommands; import glide.api.commands.StringBaseCommands; import glide.api.models.Script; import glide.api.models.commands.ExpireOptions; @@ -74,6 +76,7 @@ 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; @@ -108,6 +111,7 @@ public abstract class BaseClient ListBaseCommands, SetBaseCommands, SortedSetBaseCommands, + StreamBaseCommands, HyperLogLogBaseCommands { /** Redis simple string response with "OK" */ @@ -690,6 +694,20 @@ public CompletableFuture zrankWithScore(@NonNull String key, @NonNull 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); 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/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 6f6c2b0dd5..b56c4d1293 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -62,6 +62,7 @@ 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; @@ -82,6 +83,7 @@ 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; @@ -1465,6 +1467,40 @@ public T zrankWithScore(@NonNull String key, @NonNull String member) { 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. * 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/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index eca029a1d5..62fde73ff8 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -7,7 +7,14 @@ 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; @@ -75,6 +82,7 @@ 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; @@ -98,6 +106,7 @@ 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; @@ -109,8 +118,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 { @@ -2200,6 +2213,176 @@ public void zrankWithScore_returns_success() { 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() { 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 3a2a996192..21905a3a17 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -60,6 +60,7 @@ 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; @@ -76,6 +77,7 @@ 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; @@ -442,6 +444,28 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) .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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 5a2c984ea5..e935dd31c6 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -31,6 +31,7 @@ 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; @@ -1273,6 +1274,97 @@ public void zrank(BaseClient client) { 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") @@ -1289,16 +1381,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())); diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 4cd64cf327..3acf340f8a 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -6,6 +6,7 @@ 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; @@ -19,6 +20,7 @@ public class TransactionTestUtilities { private static final String key6 = "{key}" + 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 value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); @@ -100,6 +102,13 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact 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"}); @@ -169,6 +178,12 @@ public static Object[] transactionTestResult() { 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, From 4ffeb22eb958ca3996467f10c01b74d0426bc457 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 13:52:29 -0700 Subject: [PATCH 12/22] Java: Add `RPUSHX` and `LPUSHX` commands. (#1225) * Add `RPUSHX` and `LPUSHX` commands. (#177) Signed-off-by: Yury-Fridlyand --- glide-core/src/protobuf/redis_request.proto | 2 + glide-core/src/socket_listener.rs | 2 + .../src/main/java/glide/api/BaseClient.java | 14 +++++ .../glide/api/commands/ListBaseCommands.java | 32 +++++++++++ .../glide/api/models/BaseTransaction.java | 32 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 54 ++++++++++++++++++- .../glide/api/models/TransactionTests.java | 14 +++++ .../test/java/glide/SharedCommandTests.java | 51 ++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 6 +++ 9 files changed, 206 insertions(+), 1 deletion(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 9c76d9e887..b9385c4ce3 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -137,6 +137,8 @@ enum RequestType { Brpop = 93; Hkeys = 94; PfAdd = 96; + RPushX = 102; + LPushX = 103; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 648b9e4d40..4229be124e 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -364,6 +364,8 @@ fn get_command(request: &Command) -> Option { RequestType::Brpop => Some(cmd("BRPOP")), RequestType::Hkeys => Some(cmd("HKEYS")), RequestType::PfAdd => Some(cmd("PFADD")), + RequestType::RPushX => Some(cmd("RPUSHX")), + RequestType::LPushX => Some(cmd("LPUSHX")), } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 1bba47390d..f60513117c 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -30,6 +30,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; @@ -42,6 +43,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.PfAdd; 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.SMembers; @@ -724,6 +726,18 @@ public CompletableFuture type(@NonNull String key) { return commandManager.submitNewCommand(Type, new String[] {key}, this::handleStringResponse); } + @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) { 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 fcc0842dc1..34d085a275 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -228,4 +228,36 @@ public interface ListBaseCommands { * } */ CompletableFuture rpopCount(String key, long count); + + /** + * 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/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index b56c4d1293..05302550ca 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -39,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; @@ -52,6 +53,7 @@ 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.SMembers; @@ -1560,6 +1562,36 @@ public T type(@NonNull String key) { 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(); + } + + /** + * 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(); + } + /** * 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 diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 62fde73ff8..82123ea77d 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -58,6 +58,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; @@ -71,6 +72,7 @@ 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.SMembers; @@ -2427,6 +2429,56 @@ public void time_returns_success() { assertEquals(payload, response.get()); } + @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 pfadd_returns_success() { @@ -2449,6 +2501,6 @@ public void pfadd_returns_success() { // verify assertEquals(testResponse, response); - assertEquals(payload, response.get()); + assertEquals(value, payload); } } 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 21905a3a17..db748bb9a7 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -37,6 +37,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; @@ -50,6 +51,7 @@ 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.SMembers; @@ -475,6 +477,18 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.type("key"); results.add(Pair.of(Type, ArgsArray.newBuilder().addArgs("key").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( diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e935dd31c6..574eae1416 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1393,6 +1393,57 @@ public void type(BaseClient client) { assertTrue("stream".equalsIgnoreCase(client.type(streamKey).get())); } + @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 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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 3acf340f8a..4c8483213b 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -18,6 +18,7 @@ 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(); @@ -116,6 +117,9 @@ 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.pfadd(hllKey1, new String[] {"a", "b", "c"}); return baseTransaction; @@ -188,6 +192,8 @@ public static Object[] transactionTestResult() { Map.of("timeout", "1000"), OK, "GLIDE", // echo + 0L, // rpushx(listKey3, new String[] { "_" }) + 0L, // lpushx(listKey3, new String[] { "_" }) 1L, // pfadd(hllKey1, new String[] {"a", "b", "c"}) }; } From 334c86451b2b4bbe292c04ed9a18d61fcbe401f8 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 14:28:27 -0700 Subject: [PATCH 13/22] Java: Add `BLPOP` and `BRPOP` commands. (#1218) * Add `BLPOP` and `BRPOP` commands. (#174) Signed-off-by: Yury-Fridlyand --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + .../src/main/java/glide/api/BaseClient.java | 16 +++++ .../glide/api/commands/ListBaseCommands.java | 48 ++++++++++++++ .../glide/api/models/BaseTransaction.java | 66 ++++++++++++++++--- .../java/glide/utils/ArrayTransformUtils.java | 3 + .../test/java/glide/api/RedisClientTest.java | 52 +++++++++++++++ .../glide/api/models/TransactionTests.java | 11 ++++ .../test/java/glide/SharedCommandTests.java | 55 ++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 8 +++ 10 files changed, 252 insertions(+), 9 deletions(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index b9385c4ce3..507ec1137e 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -137,6 +137,7 @@ enum RequestType { Brpop = 93; Hkeys = 94; PfAdd = 96; + Blpop = 100; RPushX = 102; LPushX = 103; } diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 4229be124e..23b1952d75 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -366,6 +366,7 @@ fn get_command(request: &Command) -> Option { RequestType::PfAdd => Some(cmd("PFADD")), RequestType::RPushX => Some(cmd("RPUSHX")), RequestType::LPushX => Some(cmd("LPUSHX")), + RequestType::Blpop => Some(cmd("BLPOP")), } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f60513117c..97fbab3365 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; @@ -726,6 +728,20 @@ 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); 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 34d085a275..4483133ec5 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -229,6 +229,54 @@ 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. 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 05302550ca..db9ca5580a 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.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.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.ConfigGet; @@ -1563,17 +1565,25 @@ public T type(@NonNull String key) { } /** - * Inserts specified values at the tail of the list, only if key already - * exists and holds a list. + * 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. - * @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. + * @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 rpushx(@NonNull String key, @NonNull String[] elements) { - ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); - protobufTransaction.addCommands(buildCommand(RPushX, commandArgs)); + public T brpop(@NonNull String[] keys, double timeout) { + ArgsArray commandArgs = buildArgs(ArrayUtils.add(keys, Double.toString(timeout))); + protobufTransaction.addCommands(buildCommand(Brpop, commandArgs)); return getThis(); } @@ -1592,6 +1602,44 @@ public T lpushx(@NonNull String key, @NonNull String[] elements) { 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 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 82123ea77d..0ec7212025 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -25,6 +25,8 @@ 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; @@ -2429,6 +2431,31 @@ public void time_returns_success() { 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() { @@ -2479,6 +2506,31 @@ public void lpushx_returns_success() { 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() { 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 db748bb9a7..62c2e806ce 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -5,6 +5,8 @@ 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; @@ -477,6 +479,15 @@ 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( diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 574eae1416..24739ab56f 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -73,6 +73,7 @@ public static void init() { RedisClient.CreateClient( RedisClientConfiguration.builder() .address(NodeAddress.builder().port(STANDALONE_PORTS[0]).build()) + .requestTimeout(5000) .build()) .get(); @@ -1393,6 +1394,33 @@ public void type(BaseClient client) { 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") @@ -1419,6 +1447,33 @@ public void rpushx(BaseClient client) { 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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 4c8483213b..8f147a34c8 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -120,6 +120,11 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact // 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"}); return baseTransaction; @@ -194,6 +199,9 @@ public static Object[] transactionTestResult() { "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"}) }; } From 63ab900e246432f18ba5692688658f67b182f7a6 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 21:18:55 -0700 Subject: [PATCH 14/22] Java: Add `PFCOUNT` command. (#1222) * Add `PFCOUNT` command. (#167) Signed-off-by: Yury-Fridlyand --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + .../src/main/java/glide/api/BaseClient.java | 6 +++++ .../api/commands/HyperLogLogBaseCommands.java | 16 ++++++++++++ .../glide/api/models/BaseTransaction.java | 17 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 25 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 +++ .../test/java/glide/SharedCommandTests.java | 24 ++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 3 +++ 9 files changed, 97 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 507ec1137e..89056d7afa 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -137,6 +137,7 @@ enum RequestType { Brpop = 93; Hkeys = 94; PfAdd = 96; + PfCount = 97; Blpop = 100; RPushX = 102; LPushX = 103; diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 23b1952d75..02fcfa99c8 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -364,6 +364,7 @@ fn get_command(request: &Command) -> Option { RequestType::Brpop => Some(cmd("BRPOP")), RequestType::Hkeys => Some(cmd("HKEYS")), RequestType::PfAdd => Some(cmd("PFADD")), + RequestType::PfCount => Some(cmd("PFCOUNT")), RequestType::RPushX => Some(cmd("RPUSHX")), RequestType::LPushX => Some(cmd("LPUSHX")), RequestType::Blpop => Some(cmd("BLPOP")), diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 97fbab3365..3e38a3f5c5 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -43,6 +43,7 @@ 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.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.RPushX; @@ -789,4 +790,9 @@ public CompletableFuture pfadd(@NonNull String key, @NonNull String[] elem 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); + } } diff --git a/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java index 258f6c4c62..54e9ace1af 100644 --- a/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java @@ -35,4 +35,20 @@ public interface HyperLogLogBaseCommands { * } */ 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); } 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 db9ca5580a..52ccc20e25 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -52,6 +52,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -1757,6 +1758,22 @@ public T pfadd(@NonNull String key, @NonNull String[] elements) { 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(); + } + /** 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/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 0ec7212025..6a803d24f0 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -71,6 +71,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -2555,4 +2556,28 @@ public void pfadd_returns_success() { 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()); + } } 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 62c2e806ce..61667f40f2 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -50,6 +50,7 @@ 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.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -544,6 +545,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), 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())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 24739ab56f..0c6fd9770b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1641,4 +1641,28 @@ public void pfadd(BaseClient client) { 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); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 8f147a34c8..bd52c1ee06 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -23,6 +23,7 @@ public class TransactionTestUtilities { 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 value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); private static final String value3 = UUID.randomUUID().toString(); @@ -126,6 +127,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact .brpop(new String[] {listKey3}, 0.01); baseTransaction.pfadd(hllKey1, new String[] {"a", "b", "c"}); + baseTransaction.pfcount(new String[] {hllKey1, hllKey2}); return baseTransaction; } @@ -203,6 +205,7 @@ public static Object[] transactionTestResult() { 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 }); }; } } From ab4b88492b6f57f6dac63c5bcfd932b62106c02b Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 4 Apr 2024 21:59:10 -0700 Subject: [PATCH 15/22] Java: Add `PFMERGE` command. (#1224) * Add `PFMERGE` command. (#168) Signed-off-by: Yury-Fridlyand --- CHANGELOG.md | 2 +- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + .../src/main/java/glide/api/BaseClient.java | 8 +++++ .../api/commands/HyperLogLogBaseCommands.java | 21 +++++++++++++ .../glide/api/models/BaseTransaction.java | 18 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 24 +++++++++++++++ .../glide/api/models/TransactionTests.java | 6 ++++ .../test/java/glide/SharedCommandTests.java | 30 +++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 8 ++++- 10 files changed, 117 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8be4873cdf..b09023f9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,4 +83,4 @@ Preview release of **GLIDE for Redis** a Polyglot Redis client. -See the [README](README.md) for additional information. +See the [README](README.md) for additional information. \ No newline at end of file diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 89056d7afa..456faaaa82 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -138,6 +138,7 @@ enum RequestType { Hkeys = 94; PfAdd = 96; PfCount = 97; + PfMerge = 98; Blpop = 100; RPushX = 102; LPushX = 103; diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 02fcfa99c8..2ab4792f79 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -365,6 +365,7 @@ fn get_command(request: &Command) -> Option { 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")), diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 3e38a3f5c5..2dd4a62464 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -44,6 +44,7 @@ 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; @@ -795,4 +796,11 @@ public CompletableFuture pfadd(@NonNull String key, @NonNull String[] elem 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/HyperLogLogBaseCommands.java b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java index 54e9ace1af..97e4cd4cbf 100644 --- a/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HyperLogLogBaseCommands.java @@ -51,4 +51,25 @@ public interface HyperLogLogBaseCommands { * } */ 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/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 52ccc20e25..a75b5744c9 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -53,6 +53,7 @@ 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; @@ -1774,6 +1775,23 @@ public T pfcount(@NonNull String[] keys) { 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/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 6a803d24f0..1edae565cc 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -72,6 +72,7 @@ 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; @@ -2580,4 +2581,27 @@ public void pfcount_returns_success() { 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 61667f40f2..df647b2540 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -51,6 +51,7 @@ 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; @@ -547,6 +548,11 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), 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(); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 0c6fd9770b..c323c31564 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1665,4 +1665,34 @@ public void pfcount(BaseClient client) { 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 bd52c1ee06..63b6624e0d 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -24,6 +24,7 @@ public class TransactionTestUtilities { 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(); @@ -128,6 +129,9 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact 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; } @@ -205,7 +209,9 @@ public static Object[] transactionTestResult() { 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 }); + 3L, // pfcount(new String[] { hllKey1, hllKey2 });; + OK, // pfmerge(hllKey3, new String[] {hllKey1, hllKey2}) + 3L, // pfcount(new String[] { hllKey3 }) }; } } From 13024badb16cc3b26d4589b6039a2f9f93f0e90c Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:26:16 -0700 Subject: [PATCH 16/22] Java: Add `sismember` command. (Set Commands Group) (#1220) * Java: Add sismember command. (Set Commands Group) (#169) * Minor documentation update. * Minor test update. --- .../src/main/java/glide/api/BaseClient.java | 7 ++++++ .../glide/api/commands/SetBaseCommands.java | 20 +++++++++++++++ .../glide/api/models/BaseTransaction.java | 18 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 25 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 5 ++++ .../test/java/glide/SharedCommandTests.java | 19 ++++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 96 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 2dd4a62464..8c0a0c65a6 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -50,6 +50,7 @@ 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; @@ -489,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); 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 16573f883c..9ddd6766da 100644 --- a/java/client/src/main/java/glide/api/commands/SetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SetBaseCommands.java @@ -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/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index a75b5744c9..7a7a93b1a2 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -60,6 +60,7 @@ 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; @@ -811,6 +812,23 @@ public T sadd(@NonNull String key, @NonNull String[] members) { return getThis(); } + /** + * 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. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 1edae565cc..44414575ec 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -79,6 +79,7 @@ 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; @@ -1519,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() { 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 df647b2540..a4bc0eb1c9 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -58,6 +58,7 @@ 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; @@ -255,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())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index c323c31564..cb902e7de1 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -824,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") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 63b6624e0d..f8b8002cfa 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -92,6 +92,7 @@ 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)); @@ -182,6 +183,7 @@ public static Object[] transactionTestResult() { 2L, 1L, 1L, + true, // sismember(key7, "baz") Set.of("baz"), 3L, 0L, // zrank(key8, "one") From 6f94d6851a0df107e54f2429cbbf80cbe5c8768a Mon Sep 17 00:00:00 2001 From: barshaul Date: Tue, 2 Apr 2024 10:24:25 +0000 Subject: [PATCH 17/22] Fix python flakey test --- python/python/tests/test_async_client.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 0e9aeb3132..aed5847ef3 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, @@ -1779,9 +1779,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()) From d6b80c6a30226a811bb0246b6551367b920001f8 Mon Sep 17 00:00:00 2001 From: Avi Fenesh <55848801+avifenesh@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:18:13 +0300 Subject: [PATCH 18/22] Fix temporarily misalignment with new version of Cargo (#1232) https://github.com/oss-review-toolkit/ort/issues/8480 --- .github/workflows/ort.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 + From 397f9f2a489ae1efabbbc6a46d2336b4b875e9c5 Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:56:07 +0300 Subject: [PATCH 19/22] Add missing examples for Python and Node (#1227) --- node/src/BaseClient.ts | 711 +++++++++++++++++- node/src/RedisClient.ts | 71 +- node/src/RedisClusterClient.ts | 86 ++- .../glide/async_commands/cluster_commands.py | 6 +- python/python/glide/async_commands/core.py | 94 ++- 5 files changed, 920 insertions(+), 48 deletions(-) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index c5b0201d9d..12ef4ae424 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -417,6 +417,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)); @@ -431,6 +438,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, @@ -445,6 +471,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)); @@ -456,6 +497,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)); @@ -466,6 +516,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)); @@ -476,6 +533,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)); @@ -487,6 +552,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)); @@ -501,6 +574,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)); @@ -511,6 +591,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)); @@ -522,6 +610,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)); @@ -533,6 +629,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)); @@ -545,6 +656,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, @@ -562,6 +680,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)); @@ -575,6 +707,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)); @@ -588,6 +727,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)); @@ -599,6 +745,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)); @@ -610,6 +770,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)); @@ -624,6 +791,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, @@ -642,6 +816,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, @@ -656,6 +837,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)); @@ -666,6 +861,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)); @@ -679,6 +881,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)); @@ -691,6 +907,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)); @@ -703,6 +933,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)); @@ -721,6 +965,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)); @@ -732,6 +997,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)); @@ -750,6 +1022,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)); @@ -765,6 +1044,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)); @@ -778,6 +1064,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)); @@ -790,6 +1090,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)); @@ -802,6 +1116,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)); @@ -813,7 +1141,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)); @@ -824,8 +1159,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)); @@ -835,8 +1177,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)); @@ -846,7 +1195,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)); @@ -859,6 +1215,20 @@ 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)); @@ -868,8 +1238,15 @@ export class BaseClient { * 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)); @@ -881,7 +1258,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)); @@ -898,6 +1282,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, @@ -918,6 +1316,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, @@ -940,6 +1345,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, @@ -962,6 +1374,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, @@ -978,6 +1397,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)); @@ -1026,12 +1466,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, @@ -1062,11 +1508,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, @@ -1087,6 +1540,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)); @@ -1098,6 +1565,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)); @@ -1111,6 +1592,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)); @@ -1125,6 +1627,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, @@ -1140,6 +1656,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)); @@ -1150,6 +1681,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)); @@ -1165,6 +1712,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, @@ -1183,6 +1744,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, @@ -1196,6 +1771,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)); @@ -1213,6 +1809,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, @@ -1231,6 +1834,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, @@ -1250,6 +1867,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)); @@ -1264,6 +1895,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, @@ -1333,6 +1978,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)); @@ -1344,6 +2003,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)); @@ -1359,6 +2025,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)); @@ -1378,8 +2052,11 @@ export class BaseClient { * formatted as [key, value]. If no element could be popped and the timeout expired, returns Null. * * @example - * await client.brpop(["list1", "list2"], 5); - * ["list1", "element"] + * ```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"] - Indicates an element "element" was popped from "list1". + * ``` */ public brpop( keys: string[], 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/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 d78f86bf5a..410fd0b910 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,6 +266,10 @@ 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]) @@ -275,6 +285,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 +306,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 +325,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 +348,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 +369,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 +390,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 +412,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 +432,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 +457,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 +481,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") @@ -685,8 +738,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 """ @@ -732,9 +785,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( @@ -824,8 +877,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 """ @@ -871,9 +924,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( @@ -1236,6 +1289,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])) @@ -1306,6 +1361,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])) @@ -1715,9 +1773,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( @@ -1741,9 +1799,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( From ee9ef59bb3409065306c819ca5400d8307254091 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim Date: Sun, 7 Apr 2024 19:21:09 +0300 Subject: [PATCH 20/22] C#: Allow passing variable number of args to command. (#1208) * Expose request type enum which is independent from Protobuf. * Simplify C# tests by moving shared logic to function. * C#: Allow passing variable number of args to command. --- csharp/lib/AsyncClient.cs | 150 +++++++++++- csharp/lib/Message.cs | 30 ++- csharp/lib/MessageContainer.cs | 4 +- csharp/lib/src/lib.rs | 67 ++--- csharp/tests/Integration/GetAndSet.cs | 24 +- glide-core/src/lib.rs | 1 + glide-core/src/request_type.rs | 337 ++++++++++++++++++++++++++ glide-core/src/socket_listener.rs | 119 +-------- 8 files changed, 533 insertions(+), 199 deletions(-) create mode 100644 glide-core/src/request_type.rs 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/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/request_type.rs b/glide-core/src/request_type.rs new file mode 100644 index 0000000000..46a49fd20f --- /dev/null +++ b/glide-core/src/request_type.rs @@ -0,0 +1,337 @@ +/** + * 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, + 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, + } + } +} + +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")), + } + } +} diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 2ab4792f79..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,119 +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::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")), - } + let request_type: crate::request_type::RequestType = request.request_type.into(); + request_type.get_command() } fn get_redis_command(command: &Command) -> Result { From c55a67308abcc1d58186dfa81d02e1fd0aa1e531 Mon Sep 17 00:00:00 2001 From: ort-bot Date: Mon, 8 Apr 2024 00:27:05 +0000 Subject: [PATCH 21/22] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 713 +------------------------- node/THIRD_PARTY_LICENSES_NODE | 715 +------------------------- python/THIRD_PARTY_LICENSES_PYTHON | 717 +-------------------------- 3 files changed, 42 insertions(+), 2103 deletions(-) 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/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/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: From 10490fd0e496ebafca66dbcef0abb92a5b96e3f2 Mon Sep 17 00:00:00 2001 From: Gilboab <97948000+GilboaAWS@users.noreply.github.com> Date: Mon, 8 Apr 2024 09:57:56 +0300 Subject: [PATCH 22/22] Python: Added STRLEN command in python (#1230) * Python: adds STRLEN command --------- Co-authored-by: Shoham Elias --- CHANGELOG.md | 1 + python/python/glide/async_commands/core.py | 19 +++++++++++++++++++ .../glide/async_commands/transaction.py | 14 ++++++++++++++ python/python/tests/test_async_client.py | 15 +++++++++++++++ python/python/tests/test_transaction.py | 2 ++ 5 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09023f9f4..abde694c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ #### 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)) #### Fixes diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 410fd0b910..c72425197f 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -275,6 +275,25 @@ async def get(self, key: str) -> Optional[str]: 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. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index cce299dadc..263f1f6180 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. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index aed5847ef3..2509bc8656 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -979,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): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 543d3c6e64..c2d36164bc 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)