diff --git a/local_examples/cmds_generic/go-redis/cmds_generic_test.go b/local_examples/cmds_generic/go-redis/cmds_generic_test.go index 7e3c0ab6cb..e9023e235a 100644 --- a/local_examples/cmds_generic/go-redis/cmds_generic_test.go +++ b/local_examples/cmds_generic/go-redis/cmds_generic_test.go @@ -381,6 +381,7 @@ func ExampleClient_scan2_cmd() { var scan2Cursor uint64 var scan2Keys []string var err error + scan2Total := 0 for i := 0; i < 4; i++ { scan2Keys, scan2Cursor, err = rdb.Scan(ctx, scan2Cursor, "*11*", 0).Result() @@ -388,6 +389,7 @@ func ExampleClient_scan2_cmd() { if err != nil { panic(err) } + scan2Total += len(scan2Keys) } // A larger COUNT forces more scanning in a single iteration, so the remaining @@ -397,8 +399,10 @@ func ExampleClient_scan2_cmd() { if err != nil { panic(err) } + scan2Total += len(scan2Keys) - fmt.Println(len(scan2Keys)) // >>> 18 + // The per-call split isn't guaranteed, but the cumulative total is. + fmt.Println(scan2Total) // >>> 19 // STEP_END // REMOVE_START @@ -406,7 +410,7 @@ func ExampleClient_scan2_cmd() { // REMOVE_END // Output: - // 18 + // 19 } func ExampleClient_scan3_cmd() { @@ -457,14 +461,27 @@ func ExampleClient_scan3_cmd() { fmt.Println(scan3Result4) // >>> zset - scan3Result5, _, err := rdb.ScanType(ctx, 0, "", 0, "zset").Result() + // A single call isn't guaranteed to find every match, so loop until the cursor + // returns to 0, accumulating matches from every call. + var scan3Cursor uint64 + var scan3Keys []string + var scan3Batch []string - if err != nil { - panic(err) + for { + scan3Batch, scan3Cursor, err = rdb.ScanType(ctx, scan3Cursor, "", 0, "zset").Result() + + if err != nil { + panic(err) + } + scan3Keys = append(scan3Keys, scan3Batch...) + + if scan3Cursor == 0 { + break + } } - sort.Strings(scan3Result5) - fmt.Println(scan3Result5) // >>> [geokey zkey] + sort.Strings(scan3Keys) + fmt.Println(scan3Keys) // >>> [geokey zkey] // STEP_END // Output: diff --git a/local_examples/cmds_generic/ioredis/cmds-generic.js b/local_examples/cmds_generic/ioredis/cmds-generic.js index ca4325fa83..a8ff7717f7 100644 --- a/local_examples/cmds_generic/ioredis/cmds-generic.js +++ b/local_examples/cmds_generic/ioredis/cmds-generic.js @@ -58,21 +58,27 @@ await scan2Pipeline.exec(); // MATCH filters after the elements are fetched, so most iterations return nothing. let [scan2Cursor, scan2Keys] = await redis.scan(0, 'MATCH', '*11*'); +let scan2Total = scan2Keys.length; console.log(scan2Keys.length); for (let i = 0; i < 3; i++) { [scan2Cursor, scan2Keys] = await redis.scan(scan2Cursor, 'MATCH', '*11*'); + scan2Total += scan2Keys.length; console.log(scan2Keys.length); } // A larger COUNT forces more scanning in a single iteration, so the rest of the // matches arrive together. The scan continues from the cursor reached above. [scan2Cursor, scan2Keys] = await redis.scan(scan2Cursor, 'MATCH', '*11*', 'COUNT', 1000); -console.log(scan2Keys.length); // >>> 18 +scan2Total += scan2Keys.length; +console.log(scan2Keys.length); + +// The per-call split isn't guaranteed, but the cumulative total is. +console.log(scan2Total); // >>> 19 // STEP_END // REMOVE_START -assert.equal(scan2Keys.length, 18); +assert.equal(scan2Total, 19); await redis.flushdb(); // REMOVE_END @@ -86,7 +92,15 @@ console.log(scan3Res2); // >>> 1 console.log(await redis.type('geokey')); // >>> zset console.log(await redis.type('zkey')); // >>> zset -const [, scan3Keys] = await redis.scan(0, 'TYPE', 'zset'); +// A single call isn't guaranteed to find every match, so loop until the cursor +// returns to 0, accumulating matches from every call. +let scan3Cursor = '0'; +let scan3Keys = []; +do { + let scan3Batch; + [scan3Cursor, scan3Batch] = await redis.scan(scan3Cursor, 'TYPE', 'zset'); + scan3Keys = scan3Keys.concat(scan3Batch); +} while (scan3Cursor !== '0'); console.log(scan3Keys.sort()); // >>> ['geokey', 'zkey'] // STEP_END diff --git a/local_examples/cmds_generic/jedis/CmdsGenericExample.java b/local_examples/cmds_generic/jedis/CmdsGenericExample.java index 673b231a52..649d21f9e3 100644 --- a/local_examples/cmds_generic/jedis/CmdsGenericExample.java +++ b/local_examples/cmds_generic/jedis/CmdsGenericExample.java @@ -199,21 +199,27 @@ public void run() { // iterations return few keys or none at all. String scan2Cursor = "0"; ScanResult scan2Result; + int scan2Total = 0; for (int i = 0; i < 4; i++) { scan2Result = jedis.scan(scan2Cursor, new ScanParams().match("*11*")); scan2Cursor = scan2Result.getCursor(); + scan2Total += scan2Result.getResult().size(); System.out.println(scan2Result.getResult().size()); } // A larger COUNT forces more scanning in a single iteration, so the remaining // matches arrive together. This continues from the cursor reached above. scan2Result = jedis.scan(scan2Cursor, new ScanParams().match("*11*").count(1000)); - System.out.println(scan2Result.getResult().size()); // >>> 18 + scan2Total += scan2Result.getResult().size(); + System.out.println(scan2Result.getResult().size()); + + // The per-call split isn't guaranteed, but the cumulative total is. + System.out.println(scan2Total); // >>> 19 // STEP_END // REMOVE_START - assertEquals(18, scan2Result.getResult().size()); + assertEquals(19, scan2Total); jedis.flushDB(); // REMOVE_END @@ -227,10 +233,15 @@ public void run() { System.out.println(jedis.type("geokey")); // >>> zset System.out.println(jedis.type("zkey")); // >>> zset - ScanResult scan3Result3 = jedis.scan( - "0", new ScanParams(), "zset" - ); - ArrayList scan3Keys = new ArrayList<>(scan3Result3.getResult()); + // A single call isn't guaranteed to find every match, so loop until the cursor + // returns to "0", accumulating matches from every call. + String scan3Cursor = "0"; + ArrayList scan3Keys = new ArrayList<>(); + do { + ScanResult scan3Result3 = jedis.scan(scan3Cursor, new ScanParams(), "zset"); + scan3Cursor = scan3Result3.getCursor(); + scan3Keys.addAll(scan3Result3.getResult()); + } while (!scan3Cursor.equals("0")); Collections.sort(scan3Keys); System.out.println(scan3Keys); // >>> [geokey, zkey] // STEP_END diff --git a/local_examples/cmds_generic/lettuce-async/CmdsGenericExample.java b/local_examples/cmds_generic/lettuce-async/CmdsGenericExample.java index dc407f23a3..1514e68290 100644 --- a/local_examples/cmds_generic/lettuce-async/CmdsGenericExample.java +++ b/local_examples/cmds_generic/lettuce-async/CmdsGenericExample.java @@ -151,12 +151,14 @@ public void run() { // the next one needs the cursor this one returns. KeyScanCursor scan2Cursor = asyncCommands .scan(ScanArgs.Builder.matches("*11*")).toCompletableFuture().join(); + int scan2Total = scan2Cursor.getKeys().size(); System.out.println(scan2Cursor.getKeys().size()); for (int i = 0; i < 3; i++) { scan2Cursor = asyncCommands .scan(scan2Cursor, ScanArgs.Builder.matches("*11*")) .toCompletableFuture().join(); + scan2Total += scan2Cursor.getKeys().size(); System.out.println(scan2Cursor.getKeys().size()); } @@ -165,46 +167,51 @@ public void run() { scan2Cursor = asyncCommands .scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000)) .toCompletableFuture().join(); - System.out.println(scan2Cursor.getKeys().size()); // >>> 18 + scan2Total += scan2Cursor.getKeys().size(); + System.out.println(scan2Cursor.getKeys().size()); + + // The per-call split isn't guaranteed, but the cumulative total is. + System.out.println(scan2Total); // >>> 19 // STEP_END // REMOVE_START - assertThat(scan2Cursor.getKeys()).hasSize(18); + assertThat(scan2Total).isEqualTo(19); asyncCommands.flushdb().toCompletableFuture().join(); // REMOVE_END // STEP_START scan3 - CompletableFuture scan3Example = asyncCommands - .geoadd("geokey", 0, 0, "value") - .thenCompose(scan3Res1 -> { - System.out.println(scan3Res1); // >>> 1 - return asyncCommands.zadd("zkey", 1000, "value"); - }) - .thenCompose(scan3Res2 -> { - System.out.println(scan3Res2); // >>> 1 - return asyncCommands.type("geokey"); - }) - .thenCompose(scan3Res3 -> { - System.out.println(scan3Res3); // >>> zset - return asyncCommands.type("zkey"); - }) - .thenCompose(scan3Res4 -> { - System.out.println(scan3Res4); // >>> zset - return asyncCommands.scan(KeyScanArgs.Builder.type("zset")); - }) - .thenAccept(scan3Res5 -> { - List keys = new java.util.ArrayList<>(scan3Res5.getKeys()); - Collections.sort(keys); - System.out.println(keys); // >>> [geokey, zkey] - // REMOVE_START - assertThat(keys).hasSize(2); - // REMOVE_END - }) - .toCompletableFuture(); + long scan3Result1 = asyncCommands.geoadd("geokey", 0, 0, "value") + .toCompletableFuture().join(); + System.out.println(scan3Result1); // >>> 1 + + long scan3Result2 = asyncCommands.zadd("zkey", 1000, "value") + .toCompletableFuture().join(); + System.out.println(scan3Result2); // >>> 1 + + String scan3Result3 = asyncCommands.type("geokey").toCompletableFuture().join(); + System.out.println(scan3Result3); // >>> zset + + String scan3Result4 = asyncCommands.type("zkey").toCompletableFuture().join(); + System.out.println(scan3Result4); // >>> zset + + // A single call isn't guaranteed to find every match, so loop until + // the cursor is finished, accumulating matches from every call. + List scan3Keys = new java.util.ArrayList<>(); + KeyScanCursor scan3Cursor = asyncCommands + .scan(KeyScanArgs.Builder.type("zset")).toCompletableFuture().join(); + scan3Keys.addAll(scan3Cursor.getKeys()); + while (!scan3Cursor.isFinished()) { + scan3Cursor = asyncCommands + .scan(scan3Cursor, KeyScanArgs.Builder.type("zset")) + .toCompletableFuture().join(); + scan3Keys.addAll(scan3Cursor.getKeys()); + } + Collections.sort(scan3Keys); + System.out.println(scan3Keys); // >>> [geokey, zkey] // STEP_END - scan3Example.join(); // REMOVE_START + assertThat(scan3Keys).hasSize(2); asyncCommands.del("geokey", "zkey").toCompletableFuture().join(); // REMOVE_END diff --git a/local_examples/cmds_generic/lettuce-reactive/CmdsGenericExample.java b/local_examples/cmds_generic/lettuce-reactive/CmdsGenericExample.java index 207dc1ae3a..a3490a6ca7 100644 --- a/local_examples/cmds_generic/lettuce-reactive/CmdsGenericExample.java +++ b/local_examples/cmds_generic/lettuce-reactive/CmdsGenericExample.java @@ -139,11 +139,13 @@ public void run() { // turn because the next one needs the cursor this one returns. KeyScanCursor scan2Cursor = reactiveCommands .scan(ScanArgs.Builder.matches("*11*")).block(); + int scan2Total = scan2Cursor.getKeys().size(); System.out.println(scan2Cursor.getKeys().size()); for (int i = 0; i < 3; i++) { scan2Cursor = reactiveCommands .scan(scan2Cursor, ScanArgs.Builder.matches("*11*")).block(); + scan2Total += scan2Cursor.getKeys().size(); System.out.println(scan2Cursor.getKeys().size()); } @@ -151,46 +153,48 @@ public void run() { // matches arrive together. This continues from the cursor reached above. scan2Cursor = reactiveCommands .scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000)).block(); - System.out.println(scan2Cursor.getKeys().size()); // >>> 18 + scan2Total += scan2Cursor.getKeys().size(); + System.out.println(scan2Cursor.getKeys().size()); + + // The per-call split isn't guaranteed, but the cumulative total is. + System.out.println(scan2Total); // >>> 19 // STEP_END // REMOVE_START - assertThat(scan2Cursor.getKeys()).hasSize(18); + assertThat(scan2Total).isEqualTo(19); reactiveCommands.flushdb().block(); // REMOVE_END // STEP_START scan3 - Mono scan3Example = reactiveCommands - .geoadd("geokey", 0, 0, "value") - .flatMap(scan3Res1 -> { - System.out.println(scan3Res1); // >>> 1 - return reactiveCommands.zadd("zkey", 1000, "value"); - }) - .flatMap(scan3Res2 -> { - System.out.println(scan3Res2); // >>> 1 - return reactiveCommands.type("geokey"); - }) - .flatMap(scan3Res3 -> { - System.out.println(scan3Res3); // >>> zset - return reactiveCommands.type("zkey"); - }) - .flatMap(scan3Res4 -> { - System.out.println(scan3Res4); // >>> zset - return reactiveCommands.scan(KeyScanArgs.Builder.type("zset")); - }) - .doOnNext(scan3Res5 -> { - List keys = new java.util.ArrayList<>(scan3Res5.getKeys()); - Collections.sort(keys); - System.out.println(keys); // >>> [geokey, zkey] - // REMOVE_START - assertThat(keys).hasSize(2); - // REMOVE_END - }) - .then(); + long scan3Result1 = reactiveCommands.geoadd("geokey", 0, 0, "value").block(); + System.out.println(scan3Result1); // >>> 1 + + long scan3Result2 = reactiveCommands.zadd("zkey", 1000, "value").block(); + System.out.println(scan3Result2); // >>> 1 + + String scan3Result3 = reactiveCommands.type("geokey").block(); + System.out.println(scan3Result3); // >>> zset + + String scan3Result4 = reactiveCommands.type("zkey").block(); + System.out.println(scan3Result4); // >>> zset + + // A single call isn't guaranteed to find every match, so loop until + // the cursor is finished, accumulating matches from every call. + List scan3Keys = new java.util.ArrayList<>(); + KeyScanCursor scan3Cursor = reactiveCommands + .scan(KeyScanArgs.Builder.type("zset")).block(); + scan3Keys.addAll(scan3Cursor.getKeys()); + while (!scan3Cursor.isFinished()) { + scan3Cursor = reactiveCommands + .scan(scan3Cursor, KeyScanArgs.Builder.type("zset")).block(); + scan3Keys.addAll(scan3Cursor.getKeys()); + } + Collections.sort(scan3Keys); + System.out.println(scan3Keys); // >>> [geokey, zkey] // STEP_END - Mono.when(scan3Example).block(); // REMOVE_START + assertThat(scan3Keys).hasSize(2); reactiveCommands.del("geokey", "zkey").block(); // REMOVE_END diff --git a/local_examples/cmds_generic/node-redis/cmds-generic.js b/local_examples/cmds_generic/node-redis/cmds-generic.js index aada717c5d..a3ce115f6e 100644 --- a/local_examples/cmds_generic/node-redis/cmds-generic.js +++ b/local_examples/cmds_generic/node-redis/cmds-generic.js @@ -156,23 +156,33 @@ for (let i = 1; i <= 1000; i++) { let cursor = '0'; let scanResult; +let total = 0; scanResult = await client.scan(cursor, { MATCH: '*11*' }); +total += scanResult.keys.length; console.log(scanResult.cursor, scanResult.keys); scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' }); +total += scanResult.keys.length; console.log(scanResult.cursor, scanResult.keys); scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' }); +total += scanResult.keys.length; console.log(scanResult.cursor, scanResult.keys); scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*' }); +total += scanResult.keys.length; console.log(scanResult.cursor, scanResult.keys); scanResult = await client.scan(scanResult.cursor, { MATCH: '*11*', COUNT: 1000 }); +total += scanResult.keys.length; console.log(scanResult.cursor, scanResult.keys); + +// The per-call split isn't guaranteed, but the cumulative total is. +console.log(total); +// >>> 19 // REMOVE_START -console.assert(scanResult.keys.length === 18); +assert.strictEqual(total, 19); cursor = '0'; const prefix = 'key:*'; do { @@ -206,10 +216,18 @@ console.log(scan3Res4); // zset console.assert(scan3Res4 === 'zset'); // REMOVE_END -const scan3Res5 = await client.scan('0', { TYPE: 'zset' }); -console.log(scan3Res5.keys); // ['zkey', 'geokey'] +// A single call isn't guaranteed to find every match, so loop until the cursor +// returns to 0, accumulating matches from every call. +let scan3Cursor = '0'; +let scan3Keys = []; +do { + const scan3Res5 = await client.scan(scan3Cursor, { TYPE: 'zset' }); + scan3Cursor = scan3Res5.cursor; + scan3Keys = scan3Keys.concat(scan3Res5.keys); +} while (scan3Cursor !== '0'); +console.log(scan3Keys.sort()); // ['geokey', 'zkey'] // REMOVE_START -console.assert(scan3Res5.keys.sort().toString() === ['zkey', 'geokey'].sort().toString()); +assert.deepStrictEqual(scan3Keys.sort(), ['geokey', 'zkey']); await client.del(['geokey', 'zkey']); // REMOVE_END // STEP_END diff --git a/local_examples/cmds_generic/predis/CmdsGenericTest.php b/local_examples/cmds_generic/predis/CmdsGenericTest.php index 2bb4f65459..6efc22e72e 100644 --- a/local_examples/cmds_generic/predis/CmdsGenericTest.php +++ b/local_examples/cmds_generic/predis/CmdsGenericTest.php @@ -93,20 +93,26 @@ public function testCmdsGeneric() { // MATCH is applied after elements are fetched, so with the default COUNT most // iterations return few keys or none at all. $scan2Cursor = 0; + $scan2Total = 0; for ($i = 0; $i < 4; $i++) { [$scan2Cursor, $scan2Keys] = $r->scan($scan2Cursor, ['MATCH' => '*11*']); + $scan2Total += count($scan2Keys); echo count($scan2Keys) . PHP_EOL; } // A larger COUNT forces more scanning in a single iteration, so the remaining // matches arrive together. This continues from the cursor reached above. [$scan2Cursor, $scan2Keys] = $r->scan($scan2Cursor, ['MATCH' => '*11*', 'COUNT' => 1000]); - echo count($scan2Keys) . PHP_EOL; // >>> 18 + $scan2Total += count($scan2Keys); + echo count($scan2Keys) . PHP_EOL; + + // The per-call split isn't guaranteed, but the cumulative total is. + echo $scan2Total . PHP_EOL; // >>> 19 // STEP_END // REMOVE_START - $this->assertEquals(18, count($scan2Keys)); + $this->assertEquals(19, $scan2Total); $r->flushdb(); // REMOVE_END diff --git a/local_examples/cmds_generic/redis-py/cmds_generic.py b/local_examples/cmds_generic/redis-py/cmds_generic.py index 4d4493f737..2e48e62366 100644 --- a/local_examples/cmds_generic/redis-py/cmds_generic.py +++ b/local_examples/cmds_generic/redis-py/cmds_generic.py @@ -171,23 +171,34 @@ r.set(f"key:{i}", i) # REMOVE_END -cursor, key = r.scan(cursor=0, match='*11*') -print(cursor, key) +total = 0 -cursor, key = r.scan(cursor, match='*11*') -print(cursor, key) +cursor, keys = r.scan(cursor=0, match='*11*') +total += len(keys) +print(cursor, keys) -cursor, key = r.scan(cursor, match='*11*') -print(cursor, key) +cursor, keys = r.scan(cursor, match='*11*') +total += len(keys) +print(cursor, keys) -cursor, key = r.scan(cursor, match='*11*') -print(cursor, key) +cursor, keys = r.scan(cursor, match='*11*') +total += len(keys) +print(cursor, keys) + +cursor, keys = r.scan(cursor, match='*11*') +total += len(keys) +print(cursor, keys) cursor, keys = r.scan(cursor, match='*11*', count=1000) +total += len(keys) print(cursor, keys) +# The per-call split isn't guaranteed, but the cumulative total is. +print(total) +# >>> 19 + # REMOVE_START -assert len(keys) == 18 +assert total == 19 cursor = '0' prefix = "key:*" while cursor != 0: @@ -220,11 +231,19 @@ assert res == "zset" # REMOVE_END -cursor, keys = r.scan(cursor=0, _type="zset") -print(keys) -# >>> ['zkey', 'geokey'] +# A single call isn't guaranteed to find every match, so loop until the cursor +# returns to 0, accumulating matches from every call. +cursor = 0 +scan3_keys = [] +while True: + cursor, keys = r.scan(cursor=cursor, _type="zset") + scan3_keys.extend(keys) + if cursor == 0: + break +print(sorted(scan3_keys)) +# >>> ['geokey', 'zkey'] # REMOVE_START -assert sorted(keys) == sorted(["zkey", "geokey"]) +assert sorted(scan3_keys) == sorted(["zkey", "geokey"]) r.delete("geokey", "zkey") # REMOVE_END # STEP_END