From b2c0575257a8173e373cf6a1e98d5d79de1cdefe Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:15:00 +0300 Subject: [PATCH] Add NOSCORES option to ZSCAN & NOVALUES option to HSCAN (#2174) --------- Signed-off-by: Shoham Elias --- .../glide/api/commands/HashBaseCommands.java | 14 ++- .../api/commands/SortedSetBaseCommands.java | 14 ++- .../glide/api/models/BaseTransaction.java | 14 ++- .../models/commands/scan/HScanOptions.java | 46 +++++++- .../commands/scan/HScanOptionsBinary.java | 40 ++++++- .../models/commands/scan/ZScanOptions.java | 46 +++++++- .../commands/scan/ZScanOptionsBinary.java | 42 ++++++- .../test/java/glide/SharedCommandTests.java | 70 +++++++++++- .../java/glide/TransactionTestUtilities.java | 107 +++++++++++++----- node/npm/glide/index.ts | 4 + node/src/BaseClient.ts | 66 +++++++++-- node/src/Commands.ts | 41 ++++++- node/src/Transaction.ts | 22 ++-- node/tests/SharedTests.ts | 40 ++++++- node/tests/TestUtilities.ts | 37 ++++++ python/python/glide/async_commands/core.py | 54 ++++++++- .../glide/async_commands/transaction.py | 24 ++-- python/python/tests/test_async_client.py | 20 ++++ python/python/tests/test_transaction.py | 6 + 19 files changed, 617 insertions(+), 90 deletions(-) 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 9140932fc0..e732833223 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -3,7 +3,9 @@ import glide.api.models.GlideString; import glide.api.models.commands.scan.HScanOptions; +import glide.api.models.commands.scan.HScanOptions.HScanOptionsBuilder; import glide.api.models.commands.scan.HScanOptionsBinary; +import glide.api.models.commands.scan.HScanOptionsBinary.HScanOptionsBinaryBuilder; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -695,8 +697,10 @@ public interface HashBaseCommands { * cursor for the next iteration of results. "0" will be the cursor * returned on the last iteration of the result. The second element is always an * Array of the subset of the hash held in key. The array in the - * second element is always a flattened series of String pairs, where the key is - * at even indices and the value is at odd indices. + * second element is a flattened series of String pairs, where the key is at even + * indices and the value is at odd indices. If {@link HScanOptionsBuilder#noValues} is set to + * true + * , the second element will only contain the fields without the values. * @example *
{@code
      * // Assume key contains a set with 200 member-score pairs
@@ -731,8 +735,10 @@ public interface HashBaseCommands {
      *     cursor for the next iteration of results. "0" will be the cursor
      *      returned on the last iteration of the result. The second element is always an
      *     Array of the subset of the hash held in key. The array in the
-     *     second element is always a flattened series of String pairs, where the key is
-     *     at even indices and the value is at odd indices.
+     *     second element is a flattened series of String pairs, where the key is at even
+     *     indices and the value is at odd indices. If {@link HScanOptionsBinaryBuilder#noValues} is
+     *     set to true
+     *     , the second element will only contain the fields without the values.
      * @example
      *     
{@code
      * // Assume key contains a set with 200 member-score pairs
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 ce6d4c5973..814a41cc99 100644
--- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
+++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java
@@ -22,7 +22,9 @@
 import glide.api.models.commands.WeightAggregateOptions.WeightedKeys;
 import glide.api.models.commands.ZAddOptions;
 import glide.api.models.commands.scan.ZScanOptions;
+import glide.api.models.commands.scan.ZScanOptions.ZScanOptionsBuilder;
 import glide.api.models.commands.scan.ZScanOptionsBinary;
+import glide.api.models.commands.scan.ZScanOptionsBinary.ZScanOptionsBinaryBuilder;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
@@ -2789,8 +2791,10 @@ CompletableFuture> zinterWithScores(
      *      returned on the last iteration of the sorted set. The second element is always an
      *     
      *     Array of the subset of the sorted set held in key. The array in the
-     *     second element is always a flattened series of String pairs, where the value
-     *     is at even indices and the score is at odd indices.
+     *     second element is a flattened series of String pairs, where the value is at
+     *     even indices and the score is at odd indices. If {@link ZScanOptionsBuilder#noScores} is to
+     *     true
+     *     , the second element will only contain the members without scores.
      * @example
      *     
{@code
      * // Assume key contains a set with 200 member-score pairs
@@ -2826,8 +2830,10 @@ CompletableFuture> zinterWithScores(
      *      returned on the last iteration of the sorted set. The second element is always an
      *     
      *     Array of the subset of the sorted set held in key. The array in the
-     *     second element is always a flattened series of String pairs, where the value
-     *     is at even indices and the score is at odd indices.
+     *     second element is a flattened series of String pairs, where the value is at
+     *     even indices and the score is at odd indices. If {@link ZScanOptionsBinaryBuilder#noScores}
+     *     is to true
+     *     , the second element will only contain the members without scores.
      * @example
      *     
{@code
      * // Assume key contains a set with 200 member-score pairs
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 94a2ab59a8..ecbb9fac70 100644
--- a/java/client/src/main/java/glide/api/models/BaseTransaction.java
+++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java
@@ -282,8 +282,10 @@
 import glide.api.models.commands.geospatial.GeoUnit;
 import glide.api.models.commands.geospatial.GeospatialData;
 import glide.api.models.commands.scan.HScanOptions;
+import glide.api.models.commands.scan.HScanOptions.HScanOptionsBuilder;
 import glide.api.models.commands.scan.SScanOptions;
 import glide.api.models.commands.scan.ZScanOptions;
+import glide.api.models.commands.scan.ZScanOptions.ZScanOptionsBuilder;
 import glide.api.models.commands.stream.StreamAddOptions;
 import glide.api.models.commands.stream.StreamAddOptions.StreamAddOptionsBuilder;
 import glide.api.models.commands.stream.StreamClaimOptions;
@@ -7054,8 +7056,10 @@ public  T zscan(@NonNull ArgType key, @NonNull ArgType cursor) {
      *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the sorted set. The second
      *     element is always an Array of the subset of the sorted set held in key
-     *     . The array in the second element is always a flattened series of String
-     *      pairs, where the value is at even indices and the score is at odd indices.
+     *     . The array in the second element is a flattened series of String
+     *      pairs, where the value is at even indices and the score is at odd indices. If
+     *     {@link ZScanOptionsBuilder#noScores} is to true, the second element will only
+     *     contain the members without scores.
      */
     public  T zscan(
             @NonNull ArgType key, @NonNull ArgType cursor, @NonNull ZScanOptions zScanOptions) {
@@ -7101,8 +7105,10 @@ public  T hscan(@NonNull ArgType key, @NonNull ArgType cursor) {
      *     always the cursor for the next iteration of results. "0" will be
      *     the cursor returned on the last iteration of the result. The second element is
      *     always an Array of the subset of the hash held in key. The array
-     *     in the second element is always a flattened series of String pairs, where the
-     *     key is at even indices and the value is at odd indices.
+     *     in the second element is a flattened series of String pairs, where the key is
+     *     at even indices and the value is at odd indices. If {@link HScanOptionsBuilder#noValues} is
+     *     set to true, the second element will only contain the fields without the
+     *     values.
      */
     public  T hscan(
             @NonNull ArgType key, @NonNull ArgType cursor, @NonNull HScanOptions hScanOptions) {
diff --git a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java
index 1f03a00e6d..b1249748fc 100644
--- a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java
+++ b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptions.java
@@ -2,6 +2,11 @@
 package glide.api.models.commands.scan;
 
 import glide.api.commands.HashBaseCommands;
+import glide.api.models.GlideString;
+import glide.utils.ArgsBuilder;
+import java.util.Arrays;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
 import lombok.experimental.SuperBuilder;
 
 /**
@@ -10,4 +15,43 @@
  * @see valkey.io
  */
 @SuperBuilder
-public class HScanOptions extends BaseScanOptions {}
+@EqualsAndHashCode(callSuper = false)
+public class HScanOptions extends BaseScanOptions {
+
+    /** Option string to include in the HSCAN command when values are not included. */
+    public static final String NO_VALUES_API = "NOVALUES";
+
+    /**
+     * When set to true, the command will not include values in the results. This option is available
+     * from Valkey version 8.0.0 and above.
+     */
+    @Builder.Default protected boolean noValues = false;
+
+    @Override
+    public String[] toArgs() {
+        return Arrays.stream(toGlideStringArgs()).map(GlideString::getString).toArray(String[]::new);
+    }
+
+    /**
+     * Creates the arguments to be used in HSCAN commands.
+     *
+     * @return a GlideString array that holds the options and their arguments.
+     */
+    @Override
+    public GlideString[] toGlideStringArgs() {
+        ArgsBuilder builder = new ArgsBuilder();
+
+        // Add options from the superclass
+        GlideString[] superArgs = super.toGlideStringArgs();
+        for (GlideString arg : superArgs) {
+            builder.add(arg);
+        }
+
+        // Add the noValues option if applicable
+        if (noValues) {
+            builder.add(NO_VALUES_API);
+        }
+
+        return builder.toArray();
+    }
+}
diff --git a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptionsBinary.java b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptionsBinary.java
index b68506c9c4..3c42a17be9 100644
--- a/java/client/src/main/java/glide/api/models/commands/scan/HScanOptionsBinary.java
+++ b/java/client/src/main/java/glide/api/models/commands/scan/HScanOptionsBinary.java
@@ -1,7 +1,13 @@
 /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
 package glide.api.models.commands.scan;
 
+import static glide.api.models.GlideString.gs;
+
 import glide.api.commands.HashBaseCommands;
+import glide.api.models.GlideString;
+import glide.utils.ArgsBuilder;
+import java.util.Arrays;
+import lombok.Builder;
 import lombok.experimental.SuperBuilder;
 
 /**
@@ -11,4 +17,36 @@
  * @see valkey.io
  */
 @SuperBuilder
-public class HScanOptionsBinary extends BaseScanOptionsBinary {}
+public class HScanOptionsBinary extends BaseScanOptionsBinary {
+    /** Option string to include in the HSCAN command when values are not included. */
+    public static final GlideString NO_VALUES_API = gs("NOVALUES");
+
+    /**
+     * When set to true, the command will not include values in the results. This option is available
+     * from Valkey version 8.0.0 and above.
+     */
+    @Builder.Default protected boolean noValues = false;
+
+    /**
+     * Creates the arguments to be used in ZSCAN commands.
+     *
+     * @return a String array that holds the options and their arguments.
+     */
+    @Override
+    public String[] toArgs() {
+        ArgsBuilder builder = new ArgsBuilder();
+
+        // Add options from the superclass
+        String[] superArgs = super.toArgs();
+        for (String arg : superArgs) {
+            builder.add(arg);
+        }
+
+        // Add the noValues option if applicable
+        if (noValues) {
+            builder.add(NO_VALUES_API.toString());
+        }
+
+        return Arrays.stream(builder.toArray()).map(GlideString::getString).toArray(String[]::new);
+    }
+}
diff --git a/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptions.java b/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptions.java
index dbda89106b..20d63e4530 100644
--- a/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptions.java
+++ b/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptions.java
@@ -2,6 +2,11 @@
 package glide.api.models.commands.scan;
 
 import glide.api.commands.SortedSetBaseCommands;
+import glide.api.models.GlideString;
+import glide.utils.ArgsBuilder;
+import java.util.Arrays;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
 import lombok.experimental.SuperBuilder;
 
 /**
@@ -10,4 +15,43 @@
  * @see valkey.io
  */
 @SuperBuilder
-public class ZScanOptions extends BaseScanOptions {}
+@EqualsAndHashCode(callSuper = false)
+public class ZScanOptions extends BaseScanOptions {
+
+    /** Option string to include in the ZSCAN command when scores are not included. */
+    public static final String NO_SCORES_API = "NOSCORES";
+
+    /**
+     * When set to true, the command will not include scores in the results. This option is available
+     * from Valkey version 8.0.0 and above.
+     */
+    @Builder.Default protected boolean noScores = false;
+
+    @Override
+    public String[] toArgs() {
+        return Arrays.stream(toGlideStringArgs()).map(GlideString::getString).toArray(String[]::new);
+    }
+
+    /**
+     * Creates the arguments to be used in ZSCAN commands.
+     *
+     * @return a GlideString array that holds the options and their arguments.
+     */
+    @Override
+    public GlideString[] toGlideStringArgs() {
+        ArgsBuilder builder = new ArgsBuilder();
+
+        // Add options from the superclass
+        GlideString[] superArgs = super.toGlideStringArgs();
+        for (GlideString arg : superArgs) {
+            builder.add(arg);
+        }
+
+        // Add the noScores option if applicable
+        if (noScores) {
+            builder.add(NO_SCORES_API);
+        }
+
+        return builder.toArray();
+    }
+}
diff --git a/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptionsBinary.java b/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptionsBinary.java
index 288c75e4e7..d93a0b1e06 100644
--- a/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptionsBinary.java
+++ b/java/client/src/main/java/glide/api/models/commands/scan/ZScanOptionsBinary.java
@@ -1,7 +1,14 @@
 /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
 package glide.api.models.commands.scan;
 
+import static glide.api.models.GlideString.gs;
+
 import glide.api.commands.SortedSetBaseCommands;
+import glide.api.models.GlideString;
+import glide.utils.ArgsBuilder;
+import java.util.Arrays;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
 import lombok.experimental.SuperBuilder;
 
 /**
@@ -11,4 +18,37 @@
  * @see valkey.io
  */
 @SuperBuilder
-public class ZScanOptionsBinary extends BaseScanOptionsBinary {}
+@EqualsAndHashCode(callSuper = false)
+public class ZScanOptionsBinary extends BaseScanOptionsBinary {
+    /** Option string to include in the ZSCAN command when scores are not included. */
+    public static final GlideString NO_SCORES_API = gs("NOSCORES");
+
+    /**
+     * When set to true, the command will not include scores in the results. This option is available
+     * from Redis version 8.0.0 and above.
+     */
+    @Builder.Default protected boolean noScores = false;
+
+    /**
+     * Creates the arguments to be used in ZSCAN commands.
+     *
+     * @return a String array that holds the options and their arguments.
+     */
+    @Override
+    public String[] toArgs() {
+        ArgsBuilder builder = new ArgsBuilder();
+
+        // Add options from the superclass
+        String[] superArgs = super.toArgs();
+        for (String arg : superArgs) {
+            builder.add(arg);
+        }
+
+        // Add the noScores option if applicable
+        if (noScores) {
+            builder.add(NO_SCORES_API.toString());
+        }
+
+        return Arrays.stream(builder.toArray()).map(GlideString::getString).toArray(String[]::new);
+    }
+}
diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java
index aab628e7de..06be6cea3c 100644
--- a/java/integTest/src/test/java/glide/SharedCommandTests.java
+++ b/java/integTest/src/test/java/glide/SharedCommandTests.java
@@ -117,6 +117,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import lombok.Getter;
 import lombok.SneakyThrows;
 import org.apache.commons.lang3.ArrayUtils;
@@ -14055,7 +14056,7 @@ public void zscan(BaseClient client) {
         // Setup test data - use a large number of entries to force an iterative cursor.
         Map numberMap = new HashMap<>();
         for (Double i = 0.0; i < 50000; i++) {
-            numberMap.put(String.valueOf(i), i);
+            numberMap.put("member" + String.valueOf(i), i);
         }
         String[] charMembers = new String[] {"a", "b", "c", "d", "e"};
         Map charMap = new HashMap<>();
@@ -14186,11 +14187,27 @@ public void zscan(BaseClient client) {
         result =
                 client
                         .zscan(
-                                key1, initialCursor, ZScanOptions.builder().matchPattern("1*").count(20L).build())
+                                key1,
+                                initialCursor,
+                                ZScanOptions.builder().matchPattern("member1*").count(20L).build())
                         .get();
         assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
         assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0);
 
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            result =
+                    client.zscan(key1, initialCursor, ZScanOptions.builder().noScores(true).build()).get();
+            assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
+            // Cast the result collection to a String array
+            Object[] fieldsArray = (Object[]) result[resultCollectionIndex];
+            System.out.println(Arrays.toString(fieldsArray));
+            // Convert Object array to Stream for processing
+            Stream stream = Arrays.stream(fieldsArray);
+
+            // Check if all fields start with "member"
+            assertTrue(stream.allMatch(field -> ((String) field).startsWith("member")));
+        }
+
         // Exceptions
         // Non-set key
         assertEquals(OK, client.set(key2, "test").get());
@@ -14232,11 +14249,11 @@ public void zscan_binary(BaseClient client) {
         // Setup test data - use a large number of entries to force an iterative cursor.
         Map numberMap = new HashMap<>();
         for (Double i = 0.0; i < 50000; i++) {
-            numberMap.put(gs(String.valueOf(i)), i);
+            numberMap.put(gs("member" + String.valueOf(i)), i);
         }
         Map numberMap_strings = new HashMap<>();
         for (Double i = 0.0; i < 50000; i++) {
-            numberMap_strings.put(String.valueOf(i), i);
+            numberMap_strings.put("member" + String.valueOf(i), i);
         }
 
         GlideString[] charMembers = new GlideString[] {gs("a"), gs("b"), gs("c"), gs("d"), gs("e")};
@@ -14379,11 +14396,26 @@ public void zscan_binary(BaseClient client) {
                         .zscan(
                                 key1,
                                 initialCursor,
-                                ZScanOptionsBinary.builder().matchPattern(gs("1*")).count(20L).build())
+                                ZScanOptionsBinary.builder().matchPattern(gs("member1*")).count(20L).build())
                         .get();
         assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
         assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0);
 
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            result =
+                    client
+                            .zscan(key1, initialCursor, ZScanOptionsBinary.builder().noScores(true).build())
+                            .get();
+            assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
+            // Cast the result collection to a String array
+            Object[] fieldsArray = (Object[]) result[resultCollectionIndex];
+            // Convert Object array to Stream for processing
+            Stream stream = Arrays.stream(fieldsArray);
+
+            // Check if all fields start with "member"
+            assertTrue(stream.allMatch(field -> ((GlideString) field).toString().startsWith("member")));
+        }
+
         // Exceptions
         // Non-set key
         assertEquals(OK, client.set(key2, gs("test")).get());
@@ -14556,6 +14588,19 @@ public void hscan(BaseClient client) {
         assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
         assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0);
 
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            result =
+                    client.hscan(key1, initialCursor, HScanOptions.builder().noValues(true).build()).get();
+            assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
+            // Cast the result collection to a String array
+            Object[] fieldsArray = (Object[]) result[resultCollectionIndex];
+            // Convert Object array to Stream for processing
+            Stream stream = Arrays.stream(fieldsArray);
+
+            // Check if all fields dont start with "num"
+            assertTrue(stream.allMatch(field -> !((String) field).startsWith("num")));
+        }
+
         // Exceptions
         // Non-hash key
         assertEquals(OK, client.set(key2, "test").get());
@@ -14732,6 +14777,21 @@ public void hscan_binary(BaseClient client) {
         assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
         assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0);
 
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            result =
+                    client
+                            .hscan(key1, initialCursor, HScanOptionsBinary.builder().noValues(true).build())
+                            .get();
+            assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0);
+            // Cast the result collection to a String array
+            Object[] fieldsArray = (Object[]) result[resultCollectionIndex];
+            // Convert Object array to Stream for processing
+            Stream stream = Arrays.stream(fieldsArray);
+
+            // Check if all fields dont start with "num"
+            assertTrue(stream.allMatch(field -> !((GlideString) field).toString().startsWith("num")));
+        }
+
         // Exceptions
         // Non-hash key
         assertEquals(OK, client.set(key2, gs("test")).get());
diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java
index c874dbb3b1..4e41fb2751 100644
--- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java
+++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java
@@ -382,33 +382,54 @@ private static Object[] hashCommands(BaseTransaction transaction) {
                 .hscan(hashKey2, "0")
                 .hscan(hashKey2, "0", HScanOptions.builder().count(20L).build());
 
-        return new Object[] {
-            2L, // hset(hashKey1, Map.of(field1, value1, field2, value2))
-            value1, // hget(hashKey1, field1)
-            2L, // hlen(hashKey1)
-            true, // hexists(hashKey1, field2)
-            false, // hsetnx(hashKey1, field1, value1)
-            new String[] {value1, null, value2}, // hmget(hashKey1, new String[] {...})
-            Map.of(field1, value1, field2, value2), // hgetall(hashKey1)
-            1L, // hdel(hashKey1, new String[] {field1})
-            new String[] {value2}, // hvals(hashKey1)
-            field2, // hrandfield(hashKey1)
-            new String[] {field2}, // hrandfieldWithCount(hashKey1, 2)
-            new String[] {field2, field2}, // hrandfieldWithCount(hashKey1, -2)
-            new String[][] {{field2, value2}}, // hrandfieldWithCountWithValues(hashKey1, 2)
-            new String[][] {
-                {field2, value2}, {field2, value2}
-            }, // hrandfieldWithCountWithValues(hashKey1, -2)
-            5L, // hincrBy(hashKey1, field3, 5)
-            10.5, // hincrByFloat(hashKey1, field3, 5.5)
-            new String[] {field2, field3}, // hkeys(hashKey1)
-            (long) value2.length(), // hstrlen(hashKey1, field2)
-            1L, // hset(hashKey2, Map.of(field1, value1))
-            new Object[] {"0", new Object[] {field1, value1}}, // hscan(hashKey2, "0")
-            new Object[] {
-                "0", new Object[] {field1, value1}
-            }, // hscan(hashKey2, "0", HScanOptions.builder().count(20L).build());
-        };
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            transaction
+                    .hscan(hashKey2, "0", HScanOptions.builder().count(20L).noValues(false).build())
+                    .hscan(hashKey2, "0", HScanOptions.builder().count(20L).noValues(true).build());
+        }
+
+        var result =
+                new Object[] {
+                    2L, // hset(hashKey1, Map.of(field1, value1, field2, value2))
+                    value1, // hget(hashKey1, field1)
+                    2L, // hlen(hashKey1)
+                    true, // hexists(hashKey1, field2)
+                    false, // hsetnx(hashKey1, field1, value1)
+                    new String[] {value1, null, value2}, // hmget(hashKey1, new String[] {...})
+                    Map.of(field1, value1, field2, value2), // hgetall(hashKey1)
+                    1L, // hdel(hashKey1, new String[] {field1})
+                    new String[] {value2}, // hvals(hashKey1)
+                    field2, // hrandfield(hashKey1)
+                    new String[] {field2}, // hrandfieldWithCount(hashKey1, 2)
+                    new String[] {field2, field2}, // hrandfieldWithCount(hashKey1, -2)
+                    new String[][] {{field2, value2}}, // hrandfieldWithCountWithValues(hashKey1, 2)
+                    new String[][] {
+                        {field2, value2}, {field2, value2}
+                    }, // hrandfieldWithCountWithValues(hashKey1, -2)
+                    5L, // hincrBy(hashKey1, field3, 5)
+                    10.5, // hincrByFloat(hashKey1, field3, 5.5)
+                    new String[] {field2, field3}, // hkeys(hashKey1)
+                    (long) value2.length(), // hstrlen(hashKey1, field2)
+                    1L, // hset(hashKey2, Map.of(field1, value1))
+                    new Object[] {"0", new Object[] {field1, value1}}, // hscan(hashKey2, "0")
+                    new Object[] {
+                        "0", new Object[] {field1, value1}
+                    }, // hscan(hashKey2, "0", HScanOptions.builder().count(20L).build());
+                };
+
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            result =
+                    concatenateArrays(
+                            result,
+                            new Object[] {
+                                new Object[] {"0", new Object[] {field1, value1}}, // hscan(hashKey2, "0",
+                                // HScanOptions.builder().count(20L).noValues(false).build());
+                                new Object[] {"0", new Object[] {field1}}, // hscan(hashKey2, "0",
+                                // HScanOptions.builder().count(20L).noValues(true).build());
+                            });
+        }
+
+        return result;
     }
 
     private static Object[] listCommands(BaseTransaction transaction) {
@@ -648,8 +669,14 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) {
                 .zrandmemberWithCount(zSetKey2, 1)
                 .zrandmemberWithCountWithScores(zSetKey2, 1)
                 .zscan(zSetKey2, "0")
-                .zscan(zSetKey2, "0", ZScanOptions.builder().count(20L).build())
-                .bzpopmin(new String[] {zSetKey2}, .1);
+                .zscan(zSetKey2, "0", ZScanOptions.builder().count(20L).build());
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            transaction
+                    .zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(false).build())
+                    .zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(true).build());
+        }
+
+        transaction.bzpopmin(new String[] {zSetKey2}, .1);
         // zSetKey2 is now empty
 
         if (SERVER_VERSION.isGreaterThanOrEqualTo("6.2.0")) {
@@ -712,9 +739,28 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) {
                     new Object[] {
                         "0", new String[] {"one", "1"}
                     }, // zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).build())
-                    new Object[] {zSetKey2, "one", 1.0}, // bzpopmin(new String[] { zsetKey2 }, .1)
                 };
 
+        if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
+            expectedResults =
+                    concatenateArrays(
+                            expectedResults,
+                            new Object[] {
+                                new Object[] {
+                                    "0", new String[] {"one", "1"}
+                                }, // zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(false).build())
+                                new Object[] {
+                                    "0", new String[] {"one"}
+                                }, // zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(true).build())
+                            });
+        }
+
+        expectedResults =
+                concatenateArrays(
+                        expectedResults,
+                        new Object[] {
+                            new Object[] {zSetKey2, "one", 1.0}, // bzpopmin(new String[] { zsetKey2 }, .1)
+                        });
         if (SERVER_VERSION.isGreaterThanOrEqualTo("6.2.0")) {
             expectedResults =
                     concatenateArrays(
@@ -751,6 +797,7 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) {
                                 2L, // zintercard(new String[] {zSetKey4, zSetKey3}, 2)
                             });
         }
+
         return expectedResults;
     }
 
diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts
index 65840cf7a8..70458dfc8f 100644
--- a/node/npm/glide/index.ts
+++ b/node/npm/glide/index.ts
@@ -77,6 +77,8 @@ function initialize() {
     const {
         AggregationType,
         BaseScanOptions,
+        ZScanOptions,
+        HScanOptions,
         BitEncoding,
         BitFieldGet,
         BitFieldIncrBy,
@@ -185,6 +187,8 @@ function initialize() {
     module.exports = {
         AggregationType,
         BaseScanOptions,
+        HScanOptions,
+        ZScanOptions,
         BitEncoding,
         BitFieldGet,
         BitFieldIncrBy,
diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts
index b96be60469..8b394586a4 100644
--- a/node/src/BaseClient.ts
+++ b/node/src/BaseClient.ts
@@ -33,6 +33,7 @@ import {
     GeoSearchStoreResultOptions,
     GeoUnit,
     GeospatialData,
+    HScanOptions,
     InsertPosition,
     KeyWeight,
     LPosOptions,
@@ -55,6 +56,7 @@ import {
     StreamTrimOptions,
     TimeUnit,
     ZAddOptions,
+    ZScanOptions,
     convertElementsAndScores,
     createAppend,
     createBLMPop,
@@ -2049,12 +2051,13 @@ export class BaseClient {
      *
      * @param key - The key of the set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search.
-     * @param options - (Optional) The {@link BaseScanOptions}.
+     * @param options - (Optional) The {@link HScanOptions}.
      * @returns An array of the `cursor` and the subset of the hash held by `key`.
      * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor`
      * returned on the last iteration of the hash. The second element is always an array of the subset of the
-     * hash held in `key`. The array in the second element is always a flattened series of string pairs,
+     * hash held in `key`. The array in the second element is a flattened series of string pairs,
      * where the value is at even indices and the value is at odd indices.
+     * If `options.noValues` is set to `true`, the second element will only contain the fields without the values.
      *
      * @example
      * ```typescript
@@ -2076,13 +2079,36 @@ export class BaseClient {
      * // Cursor:  39
      * // Members:  ['field 63', 'value 63', 'field 293', 'value 293', 'field 162', 'value 162']
      * // Cursor:  0
-     * // Members:  ['value 55', '55', 'value 24', '24', 'value 90', '90', 'value 113', '113']
+     * // Members:  ['field 55', 'value 55', 'field 24', 'value 24', 'field 90', 'value 90', 'field 113', 'value 113']
+     * ```
+     * @example
+     * ```typescript
+     * // Hscan with noValues
+     * let newCursor = "0";
+     * let result = [];
+     * do {
+     *      result = await client.hscan(key1, newCursor, {
+     *          match: "*",
+     *          count: 3,
+     *          noValues: true,
+     *      });
+     *      newCursor = result[0];
+     *      console.log("Cursor: ", newCursor);
+     *      console.log("Members: ", result[1]);
+     * } while (newCursor !== "0");
+     * // The output of the code above is something similar to:
+     * // Cursor:  31
+     * // Members:  ['field 79', 'field 20', 'field 115']
+     * // Cursor:  39
+     * // Members:  ['field 63', 'field 293', 'field 162']
+     * // Cursor:  0
+     * // Members:  ['field 55', 'field 24', 'field 90', 'field 113']
      * ```
      */
     public async hscan(
         key: string,
         cursor: string,
-        options?: BaseScanOptions,
+        options?: HScanOptions,
     ): Promise<[string, string[]]> {
         return this.createWritePromise(createHScan(key, cursor, options));
     }
@@ -6442,12 +6468,13 @@ export class BaseClient {
      * @param key - The key of the sorted set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of
      *      the search.
-     * @param options - (Optional) The `zscan` options - see {@link BaseScanOptions} and {@link DecoderOption}.
+     * @param options - (Optional) The `zscan` options - see {@link ZScanOptions} and {@link DecoderOption}.
      * @returns An `Array` of the `cursor` and the subset of the sorted set held by `key`.
      *      The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
      *      returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
-     *      of the sorted set held in `key`. The `Array` in the second element is always a flattened series of
+     *      of the sorted set held in `key`. The `Array` in the second element is a flattened series of
      *      `string` pairs, where the value is at even indices and the score is at odd indices.
+     *      If `options.noScores` is to `true`, the second element will only contain the members without scores.
      *
      * @example
      * ```typescript
@@ -6470,11 +6497,36 @@ export class BaseClient {
      * // Cursor:  0
      * // Members:  ['value 55', '55', 'value 24', '24', 'value 90', '90', 'value 113', '113']
      * ```
+     *
+     * @example
+     * ```typescript
+     * // Zscan with no scores
+     * let newCursor = "0";
+     * let result = [];
+     *
+     * do {
+     *      result = await client.zscan(key1, newCursor, {
+     *          match: "*",
+     *          count: 5,
+     *          noScores: true,
+     *      });
+     *      newCursor = result[0];
+     *      console.log("Cursor: ", newCursor);
+     *      console.log("Members: ", result[1]);
+     * } while (newCursor !== "0");
+     * // The output of the code above is something similar to:
+     * // Cursor:  123
+     * // Members:  ['value 163', 'value 114', 'value 25', 'value 82', 'value 64']
+     * // Cursor:  47
+     * // Members:  ['value 39', 'value 127', 'value 43', 'value 139', 'value 211']
+     * // Cursor:  0
+     * // Members:  ['value 55', 'value 24' 'value 90', 'value 113']
+     * ```
      */
     public async zscan(
         key: GlideString,
         cursor: string,
-        options?: BaseScanOptions & DecoderOption,
+        options?: ZScanOptions & DecoderOption,
     ): Promise<[string, GlideString[]]> {
         return this.createWritePromise<[GlideString, GlideString[]]>(
             createZScan(key, cursor, options),
diff --git a/node/src/Commands.ts b/node/src/Commands.ts
index e7be32de14..2d26178332 100644
--- a/node/src/Commands.ts
+++ b/node/src/Commands.ts
@@ -3669,12 +3669,16 @@ export function createHRandField(
 export function createHScan(
     key: string,
     cursor: string,
-    options?: BaseScanOptions,
+    options?: HScanOptions,
 ): command_request.Command {
     let args: GlideString[] = [key, cursor];
 
     if (options) {
         args = args.concat(convertBaseScanOptionsToArgsArray(options));
+
+        if (options.noValues) {
+            args.push("NOVALUES");
+        }
     }
 
     return createCommand(RequestType.HScan, args);
@@ -3764,9 +3768,8 @@ export function createWait(
 }
 
 /**
- * This base class represents the common set of optional arguments for the `SCAN` family of commands.
- * Concrete implementations of this class are tied to specific SCAN commands (`SCAN`, `HSCAN`, `SSCAN`,
- * and `ZSCAN`).
+ * This base class represents the common set of optional arguments for the SCAN family of commands.
+ * Concrete implementations of this class are tied to specific SCAN commands (`SCAN`, `SSCAN`).
  */
 export type BaseScanOptions = {
     /**
@@ -3782,7 +3785,29 @@ export type BaseScanOptions = {
      * sorted set. `COUNT` could be ignored until the sorted set is large enough for the `SCAN` commands to
      * represent the results as compact single-allocation packed encoding.
      */
-    count?: number;
+    readonly count?: number;
+};
+
+/**
+ * Options specific to the ZSCAN command, extending from the base scan options.
+ */
+export type ZScanOptions = BaseScanOptions & {
+    /**
+     * If true, the scores are not included in the results.
+     * Supported from Valkey 8.0.0 and above.
+     */
+    readonly noScores?: boolean;
+};
+
+/**
+ * Options specific to the HSCAN command, extending from the base scan options.
+ */
+export type HScanOptions = BaseScanOptions & {
+    /**
+     * If true, the values of the fields are not included in the results.
+     * Supported from Valkey 8.0.0 and above.
+     */
+    readonly noValues?: boolean;
 };
 
 /**
@@ -3810,12 +3835,16 @@ function convertBaseScanOptionsToArgsArray(
 export function createZScan(
     key: GlideString,
     cursor: string,
-    options?: BaseScanOptions,
+    options?: ZScanOptions,
 ): command_request.Command {
     let args = [key, cursor];
 
     if (options) {
         args = args.concat(convertBaseScanOptionsToArgsArray(options));
+
+        if (options.noScores) {
+            args.push("NOSCORES");
+        }
     }
 
     return createCommand(RequestType.ZScan, args);
diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts
index 9503adbe0a..87d86f8db3 100644
--- a/node/src/Transaction.ts
+++ b/node/src/Transaction.ts
@@ -41,6 +41,7 @@ import {
     GeoSearchStoreResultOptions,
     GeoUnit,
     GeospatialData,
+    HScanOptions,
     InfoOptions,
     InsertPosition,
     KeyWeight,
@@ -67,6 +68,7 @@ import {
     StreamTrimOptions,
     TimeUnit,
     ZAddOptions,
+    ZScanOptions,
     convertElementsAndScores,
     createAppend,
     createBLMPop,
@@ -996,15 +998,16 @@ export class BaseTransaction> {
      *
      * @param key - The key of the set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of the search.
-     * @param options - (Optional) The {@link BaseScanOptions}.
+     * @param options - (Optional) The {@link HScanOptions}.
      *
-     * Command Response -  An array of the `cursor` and the subset of the hash held by `key`.
+     * Command Response - An array of the `cursor` and the subset of the hash held by `key`.
      * The first element is always the `cursor` for the next iteration of results. `"0"` will be the `cursor`
      * returned on the last iteration of the hash. The second element is always an array of the subset of the
-     * hash held in `key`. The array in the second element is always a flattened series of string pairs,
+     * hash held in `key`. The array in the second element is a flattened series of string pairs,
      * where the value is at even indices and the value is at odd indices.
+     * If `options.noValues` is set to `true`, the second element will only contain the fields without the values.
      */
-    public hscan(key: string, cursor: string, options?: BaseScanOptions): T {
+    public hscan(key: string, cursor: string, options?: HScanOptions): T {
         return this.addAndReturn(createHScan(key, cursor, options));
     }
 
@@ -3675,19 +3678,16 @@ export class BaseTransaction> {
      * @param key - The key of the sorted set.
      * @param cursor - The cursor that points to the next iteration of results. A value of `"0"` indicates the start of
      *      the search.
-     * @param options - (Optional) The `zscan` options - see {@link BaseScanOptions}
+     * @param options - (Optional) The `zscan` options - see {@link ZScanOptions}
      *
      * Command Response - An `Array` of the `cursor` and the subset of the sorted set held by `key`.
      *      The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
      *      returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
-     *      of the sorted set held in `key`. The `Array` in the second element is always a flattened series of
+     *      of the sorted set held in `key`. The `Array` in the second element is a flattened series of
      *      `String` pairs, where the value is at even indices and the score is at odd indices.
+     *      If `options.noScores` is to `true`, the second element will only contain the members without scores.
      */
-    public zscan(
-        key: GlideString,
-        cursor: string,
-        options?: BaseScanOptions,
-    ): T {
+    public zscan(key: GlideString, cursor: string, options?: ZScanOptions): T {
         return this.addAndReturn(createZScan(key, cursor, options));
     }
 
diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts
index 89987d1c92..28cd31d95b 100644
--- a/node/tests/SharedTests.ts
+++ b/node/tests/SharedTests.ts
@@ -1415,7 +1415,7 @@ export function runBaseTests(config: {
     it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
         `hscan test_%p`,
         async (protocol) => {
-            await runTest(async (client: BaseClient) => {
+            await runTest(async (client: BaseClient, cluster) => {
                 const key1 = "{key}-1" + uuidv4();
                 const initialCursor = "0";
                 const defaultCount = 20;
@@ -1556,6 +1556,22 @@ export function runBaseTests(config: {
                 });
                 expect(result[resultCursorIndex]).not.toEqual(initialCursor);
                 expect(result[resultCollectionIndex].length).toBeGreaterThan(0);
+
+                if (!cluster.checkIfServerVersionLessThan("7.9.0")) {
+                    const result = await client.hscan(key1, initialCursor, {
+                        noValues: true,
+                    });
+                    const resultCursor = result[resultCursorIndex];
+                    const fieldsArray = result[
+                        resultCollectionIndex
+                    ] as string[];
+
+                    // Verify that the cursor is not "0" and values are not included
+                    expect(resultCursor).not.toEqual("0");
+                    expect(
+                        fieldsArray.every((field) => !field.startsWith("num")),
+                    ).toBeTruthy();
+                }
             }, protocol);
         },
         config.timeout,
@@ -9256,7 +9272,7 @@ export function runBaseTests(config: {
                 const numberMap: Record = {};
 
                 for (let i = 0; i < 50000; i++) {
-                    numberMap[i.toString()] = i;
+                    numberMap["member" + i.toString()] = i;
                 }
 
                 const charMembers = ["a", "b", "c", "d", "e"];
@@ -9369,12 +9385,30 @@ export function runBaseTests(config: {
 
                 // Test count with match returns a non-empty list
                 result = await client.zscan(key1, initialCursor, {
-                    match: "1*",
+                    match: "member1*",
                     count: 20,
                 });
                 expect(result[resultCursorIndex]).not.toEqual("0");
                 expect(result[resultCollectionIndex].length).toBeGreaterThan(0);
 
+                if (!cluster.checkIfServerVersionLessThan("7.9.0")) {
+                    const result = await client.zscan(key1, initialCursor, {
+                        noScores: true,
+                    });
+                    const resultCursor = result[resultCursorIndex];
+                    const fieldsArray = result[
+                        resultCollectionIndex
+                    ] as string[];
+
+                    // Verify that the cursor is not "0" and values are not included
+                    expect(resultCursor).not.toEqual("0");
+                    expect(
+                        fieldsArray.every((field) =>
+                            field.startsWith("member"),
+                        ),
+                    ).toBeTruthy();
+                }
+
                 // Exceptions
                 // Non-set key
                 expect(await client.set(key2, "test")).toEqual("OK");
diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts
index 7136c043ab..5bfbae0013 100644
--- a/node/tests/TestUtilities.ts
+++ b/node/tests/TestUtilities.ts
@@ -766,6 +766,24 @@ export async function transactionTest(
     responseData.push(["hset(key4, { [field]: value })", 1]);
     baseTransaction.hscan(key4, "0");
     responseData.push(['hscan(key4, "0")', ["0", [field, value]]]);
+
+    if (gte(version, "7.9.0")) {
+        baseTransaction.hscan(key4, "0", { noValues: false });
+        responseData.push([
+            'hscan(key4, "0", {noValues: false})',
+            ["0", [field, value]],
+        ]);
+        baseTransaction.hscan(key4, "0", {
+            match: "*",
+            count: 20,
+            noValues: true,
+        });
+        responseData.push([
+            'hscan(key4, "0", {match: "*", count: 20, noValues:true})',
+            ["0", [field]],
+        ]);
+    }
+
     baseTransaction.hscan(key4, "0", { match: "*", count: 20 });
     responseData.push([
         'hscan(key4, "0", {match: "*", count: 20})',
@@ -1009,6 +1027,25 @@ export async function transactionTest(
     responseData.push(["zadd(key12, { one: 1, two: 2 })", 2]);
     baseTransaction.zscan(key12, "0");
     responseData.push(['zscan(key12, "0")', ["0", ["one", "1", "two", "2"]]]);
+
+    if (gte(version, "7.9.0")) {
+        baseTransaction.zscan(key12, "0", { noScores: false });
+        responseData.push([
+            'zscan(key12, "0", {noScores: false})',
+            ["0", ["one", "1", "two", "2"]],
+        ]);
+
+        baseTransaction.zscan(key12, "0", {
+            match: "*",
+            count: 20,
+            noScores: true,
+        });
+        responseData.push([
+            'zscan(key12, "0", {match: "*", count: 20, noScores:true})',
+            ["0", ["one", "two"]],
+        ]);
+    }
+
     baseTransaction.zscan(key12, "0", { match: "*", count: 20 });
     responseData.push([
         'zscan(key12, "0", {match: "*", count: 20})',
diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py
index c3bb4e1aea..ba8fd51f74 100644
--- a/python/python/glide/async_commands/core.py
+++ b/python/python/glide/async_commands/core.py
@@ -6117,6 +6117,7 @@ async def zscan(
         cursor: TEncodable,
         match: Optional[TEncodable] = None,
         count: Optional[int] = None,
+        no_scores: bool = False,
     ) -> List[Union[bytes, List[bytes]]]:
         """
         Iterates incrementally over a sorted set.
@@ -6135,13 +6136,15 @@ async def zscan(
             count (Optional[int]): `COUNT` is a just a hint for the command for how many elements to fetch from the
                 sorted set. `COUNT` could be ignored until the sorted set is large enough for the `SCAN` commands to
                 represent the results as compact single-allocation packed encoding.
+            no_scores (bool): If `True`, the command will not return scores associated with the members. Since Valkey "8.0.0".
 
         Returns:
             List[Union[bytes, List[bytes]]]: An `Array` of the `cursor` and the subset of the sorted set held by `key`.
-                The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
-                returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
-                of the sorted set held in `key`. The `Array` in the second element is always a flattened series of
-                `String` pairs, where the value is at even indices and the score is at odd indices.
+            The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
+            returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
+            of the sorted set held in `key`. The `Array` in the second element is a flattened series of
+            `String` pairs, where the value is at even indices and the score is at odd indices.
+            If `no_scores` is set to`True`, the second element will only contain the members without scores.
 
         Examples:
             # Assume "key" contains a sorted set with multiple members
@@ -6160,12 +6163,31 @@ async def zscan(
             Members: [b'value 39', b'39', b'value 127', b'127', b'value 43', b'43', b'value 139', b'139', b'value 211', b'211']
             Cursor: 0
             Members: [b'value 55', b'55', b'value 24', b'24', b'value 90', b'90', b'value 113', b'113']
+
+            # Using no-score
+            >>> result_cursor = "0"
+            >>> while True:
+            ...     result = await client.zscan("key", "0", match="*", count=5, no_scores=True)
+            ...     new_cursor = str(result[0])
+            ...     print("Cursor: ", new_cursor)
+            ...     print("Members: ", result[1])
+            ...     if new_cursor == "0":
+            ...         break
+            ...     result_cursor = new_cursor
+            Cursor: 123
+            Members: [b'value 163', b'value 114', b'value 25', b'value 82', b'value 64']
+            Cursor: 47
+            Members: [b'value 39', b'value 127', b'value 43', b'value 139', b'value 211']
+            Cursor: 0
+            Members: [b'value 55', b'value 24', b'value 90', b'value 113']
         """
         args: List[TEncodable] = [key, cursor]
         if match is not None:
             args += ["MATCH", match]
         if count is not None:
             args += ["COUNT", str(count)]
+        if no_scores:
+            args.append("NOSCORES")
 
         return cast(
             List[Union[bytes, List[bytes]]],
@@ -6178,6 +6200,7 @@ async def hscan(
         cursor: TEncodable,
         match: Optional[TEncodable] = None,
         count: Optional[int] = None,
+        no_values: bool = False,
     ) -> List[Union[bytes, List[bytes]]]:
         """
         Iterates incrementally over a hash.
@@ -6196,13 +6219,15 @@ async def hscan(
             count (Optional[int]): `COUNT` is a just a hint for the command for how many elements to fetch from the hash.
                 `COUNT` could be ignored until the hash is large enough for the `SCAN` commands to represent the results
                 as compact single-allocation packed encoding.
+            no_values (bool): If `True`, the command will not return values the fields in the hash. Since Valkey "8.0.0".
 
         Returns:
             List[Union[bytes, List[bytes]]]: An `Array` of the `cursor` and the subset of the hash held by `key`.
                 The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
                 returned on the last iteration of the hash. The second element is always an `Array` of the subset of the
-                hash held in `key`. The `Array` in the second element is always a flattened series of `String` pairs,
+                hash held in `key`. The `Array` in the second element is a flattened series of `String` pairs,
                 where the value is at even indices and the score is at odd indices.
+                If `no_values` is set to `True`, the second element will only contain the fields without the values.
 
         Examples:
             # Assume "key" contains a hash with multiple members
@@ -6221,12 +6246,31 @@ async def hscan(
             Members: [b'field 63', b'value 63', b'field 293', b'value 293', b'field 162', b'value 162']
             Cursor: 0
             Members: [b'field 420', b'value 420', b'field 221', b'value 221']
+
+            # Use no-values
+            >>> result_cursor = "0"
+            >>> while True:
+            ...     result = await client.hscan("key", "0", match="*", count=3, no_values=True)
+            ...     new_cursor = str(result [0])
+            ...     print("Cursor: ", new_cursor)
+            ...     print("Members: ", result[1])
+            ...     if new_cursor == "0":
+            ...         break
+            ...     result_cursor = new_cursor
+            Cursor: 1
+            Members: [b'field 79',b'field 20', b'field 115']
+            Cursor: 39
+            Members: [b'field 63', b'field 293', b'field 162']
+            Cursor: 0
+            Members: [b'field 420', b'field 221']
         """
         args: List[TEncodable] = [key, cursor]
         if match is not None:
             args += ["MATCH", match]
         if count is not None:
             args += ["COUNT", str(count)]
+        if no_values:
+            args.append("NOVALUES")
 
         return cast(
             List[Union[bytes, List[bytes]]],
diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py
index 8aa0f0fa1d..8201c482bc 100644
--- a/python/python/glide/async_commands/transaction.py
+++ b/python/python/glide/async_commands/transaction.py
@@ -4463,6 +4463,7 @@ def zscan(
         cursor: TEncodable,
         match: Optional[TEncodable] = None,
         count: Optional[int] = None,
+        no_scores: bool = False,
     ) -> TTransaction:
         """
         Iterates incrementally over a sorted set.
@@ -4474,26 +4475,30 @@ def zscan(
             cursor (TEncodable): The cursor that points to the next iteration of results. A value of "0" indicates the start of
                 the search.
             match (Optional[TEncodable]): The match filter is applied to the result of the command and will only include
-                strings or byte string that match the pattern specified. If the sorted set is large enough for scan commands to return
+                strings or byte strings that match the pattern specified. If the sorted set is large enough for scan commands to return
                 only a subset of the sorted set then there could be a case where the result is empty although there are
                 items that match the pattern specified. This is due to the default `COUNT` being `10` which indicates
                 that it will only fetch and match `10` items from the list.
             count (Optional[int]): `COUNT` is a just a hint for the command for how many elements to fetch from the
                 sorted set. `COUNT` could be ignored until the sorted set is large enough for the `SCAN` commands to
                 represent the results as compact single-allocation packed encoding.
+            no_scores (bool): If `True`, the command will not return scores associated with the members. Since Valkey "8.0.0".
 
         Returns:
             List[Union[bytes, List[bytes]]]: An `Array` of the `cursor` and the subset of the sorted set held by `key`.
-                The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
-                returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
-                of the sorted set held in `key`. The `Array` in the second element is always a flattened series of
-                `String` pairs, where the value is at even indices and the score is at odd indices.
+            The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
+            returned on the last iteration of the sorted set. The second element is always an `Array` of the subset
+            of the sorted set held in `key`. The `Array` in the second element is a flattened series of
+            `String` pairs, where the value is at even indices and the score is at odd indices.
+            If `no_scores` is set to`True`, the second element will only contain the members without scores.
         """
         args = [key, cursor]
         if match is not None:
             args += ["MATCH", match]
         if count is not None:
             args += ["COUNT", str(count)]
+        if no_scores:
+            args.append("NOSCORES")
 
         return self.append_command(RequestType.ZScan, args)
 
@@ -4503,6 +4508,7 @@ def hscan(
         cursor: TEncodable,
         match: Optional[TEncodable] = None,
         count: Optional[int] = None,
+        no_values: bool = False,
     ) -> TTransaction:
         """
         Iterates incrementally over a hash.
@@ -4514,26 +4520,30 @@ def hscan(
             cursor (TEncodable): The cursor that points to the next iteration of results. A value of "0" indicates the start of
                 the search.
             match (Optional[TEncodable]): The match filter is applied to the result of the command and will only include
-                strings or bytes strings that match the pattern specified. If the hash is large enough for scan commands to return only a
+                strings or byte strings that match the pattern specified. If the hash is large enough for scan commands to return only a
                 subset of the hash then there could be a case where the result is empty although there are items that
                 match the pattern specified. This is due to the default `COUNT` being `10` which indicates that it will
                 only fetch and match `10` items from the list.
             count (Optional[int]): `COUNT` is a just a hint for the command for how many elements to fetch from the hash.
                 `COUNT` could be ignored until the hash is large enough for the `SCAN` commands to represent the results
                 as compact single-allocation packed encoding.
+            no_values (bool): If `True`, the command will not return values the fields in the hash. Since Valkey "8.0.0".
 
         Returns:
             List[Union[bytes, List[bytes]]]: An `Array` of the `cursor` and the subset of the hash held by `key`.
                 The first element is always the `cursor` for the next iteration of results. `0` will be the `cursor`
                 returned on the last iteration of the hash. The second element is always an `Array` of the subset of the
-                hash held in `key`. The `Array` in the second element is always a flattened series of `String` pairs,
+                hash held in `key`. The `Array` in the second element is a flattened series of `String` pairs,
                 where the value is at even indices and the score is at odd indices.
+                If `no_values` is set to `True`, the second element will only contain the fields without the values.
         """
         args = [key, cursor]
         if match is not None:
             args += ["MATCH", match]
         if count is not None:
             args += ["COUNT", str(count)]
+        if no_values:
+            args.append("NOVALUES")
 
         return self.append_command(RequestType.HScan, args)
 
diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py
index a5026e70dc..9e3664ba08 100644
--- a/python/python/tests/test_async_client.py
+++ b/python/python/tests/test_async_client.py
@@ -10020,6 +10020,16 @@ async def test_zscan(self, glide_client: GlideClusterClient):
         assert result[result_cursor_index] != b"0"
         assert len(result[result_collection_index]) >= 0
 
+        # Test no_scores option
+        if not await check_if_server_version_lt(glide_client, "7.9.0"):
+            result = await glide_client.zscan(key1, initial_cursor, no_scores=True)
+            assert result[result_cursor_index] != b"0"
+            values_array = cast(List[bytes], result[result_collection_index])
+            # Verify that scores are not included
+            assert all(
+                item.startswith(b"value") and item.isascii() for item in values_array
+            )
+
         # Exceptions
         # Non-set key
         assert await glide_client.set(key2, "test") == OK
@@ -10137,6 +10147,16 @@ async def test_hscan(self, glide_client: GlideClusterClient):
         assert result[result_cursor_index] != b"0"
         assert len(result[result_collection_index]) >= 0
 
+        # Test no_values option
+        if not await check_if_server_version_lt(glide_client, "7.9.0"):
+            result = await glide_client.hscan(key1, initial_cursor, no_values=True)
+            assert result[result_cursor_index] != b"0"
+            values_array = cast(List[bytes], result[result_collection_index])
+            # Verify that values are not included
+            assert all(
+                item.startswith(b"field") and item.isascii() for item in values_array
+            )
+
         # Exceptions
         # Non-hash key
         assert await glide_client.set(key2, "test") == OK
diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py
index a279b76f87..964b1309a9 100644
--- a/python/python/tests/test_transaction.py
+++ b/python/python/tests/test_transaction.py
@@ -306,6 +306,9 @@ async def transaction_test(
     args.append([b"0", [key3.encode(), b"10.5"]])
     transaction.hscan(key4, "0", match="*", count=10)
     args.append([b"0", [key3.encode(), b"10.5"]])
+    if not await check_if_server_version_lt(glide_client, "7.9.0"):
+        transaction.hscan(key4, "0", match="*", count=10, no_values=True)
+        args.append([b"0", [key3.encode()]])
     transaction.hrandfield(key4)
     args.append(key3_bytes)
     transaction.hrandfield_count(key4, 1)
@@ -458,6 +461,9 @@ async def transaction_test(
     args.append([b"0", [b"three", b"3"]])
     transaction.zscan(key8, "0", match="*", count=20)
     args.append([b"0", [b"three", b"3"]])
+    if not await check_if_server_version_lt(glide_client, "7.9.0"):
+        transaction.zscan(key8, "0", match="*", count=20, no_scores=True)
+        args.append([b"0", [b"three"]])
     transaction.zpopmax(key8)
     args.append({b"three": 3.0})
     transaction.zpopmin(key8)