SCAN
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- Available since:
- Redis Open Source 2.8.0
- Time complexity:
- O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.
- ACL categories:
-
@keyspace,@read,@slow, - Compatibility:
- Redis Software and Redis Cloud compatibility
The SCAN command and the closely related commands SSCAN, HSCAN and ZSCAN are used in order to incrementally iterate over a collection of elements.
SCANiterates the set of keys in the currently selected Redis database.SSCANiterates elements of Sets types.HSCANiterates fields of Hash types and their associated values.ZSCANiterates elements of Sorted Set types and their associated scores.
Since these commands allow for incremental iteration, returning only a small number of elements per call, they can be used in production without the downside of commands like KEYS or SMEMBERS that may block the server for a long time (even several seconds) when called against big collections of keys or elements.
However while blocking commands like SMEMBERS are able to provide all the elements that are part of a Set in a given moment, The SCAN family of commands only offer limited guarantees about the returned elements since the collection that we incrementally iterate can change during the iteration process.
Note that SCAN, SSCAN, HSCAN and ZSCAN all work very similarly, so this documentation covers all four commands. However an obvious difference is that in the case of SSCAN, HSCAN and ZSCAN the first argument is the name of the key holding the Set, Hash or Sorted Set value. The SCAN command does not need any key name argument as it iterates keys in the current database, so the iterated object is the database itself.
Required arguments
cursor
The cursor value. Start an iteration with 0, then use the cursor returned by each call as the argument to the next call.
Optional arguments
MATCH pattern
Only return keys matching the given glob-style pattern.
COUNT count
A hint for the number of keys to return per iteration. The default is 10.
TYPE type
Only return keys of the given type, such as string, list, or set.
Details
SCAN basic usage
SCAN is a cursor based iterator. This means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call.
An iteration starts when the cursor is set to 0, and terminates when the cursor returned by the server is 0. The following is an example of SCAN iteration:
> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
In the example above, the first call uses zero as a cursor, to start the iteration. The second call uses the cursor returned by the previous call as the first element of the reply, that is, 17.
As you can see the SCAN return value is an array of two values: the first value is the new cursor to use in the next call, the second value is an array of elements.
Since in the second call the returned cursor is 0, the server signaled to the caller that the iteration finished, and the collection was completely explored. Starting an iteration with a cursor value of 0, and calling SCAN until the returned cursor is 0 again is called a full iteration.
Return value
SCAN, SSCAN, HSCAN and ZSCAN return a two element multi-bulk reply, where the first element is a string representing an unsigned 64 bit number (the cursor), and the second element is a multi-bulk with an array of elements.
SCANarray of elements is a list of keys.SSCANarray of elements is a list of Set members.HSCANarray of elements contain two elements, a field and a value, for every returned element of the Hash.ZSCANarray of elements contain two elements, a member and its associated score, for every returned element of the Sorted Set.
Scan guarantees
The SCAN command, and the other commands in the SCAN family, are able to provide to the user a set of guarantees associated to full iterations.
- A full iteration always retrieves all the elements that were present in the collection from the start to the end of a full iteration. This means that if a given element is inside the collection when an iteration is started, and is still there when an iteration terminates, then at some point
SCANreturned it to the user. - A full iteration never returns any element that was NOT present in the collection from the start to the end of a full iteration. So if an element was removed before the start of an iteration, and is never added back to the collection for all the time an iteration lasts,
SCANensures that this element will never be returned.
However because SCAN has very little state associated (just the cursor) it has the following drawbacks:
- A given element may be returned multiple times. It is up to the application to handle the case of duplicated elements, for example only using the returned elements in order to perform operations that are safe when re-applied multiple times.
- Elements that were not constantly present in the collection during a full iteration, may be returned or not: it is undefined.
Number of elements returned at every SCAN call
SCAN family functions do not guarantee that the number of elements returned per call are in a given range. The commands are also allowed to return zero elements, and the client should not consider the iteration complete as long as the returned cursor is not zero.
However the number of returned elements is reasonable, that is, in practical terms SCAN may return a maximum number of elements in the order of a few tens of elements when iterating a large collection, or may return all the elements of the collection in a single call when the iterated collection is small enough to be internally represented as an encoded data structure (this happens for small Sets, Hashes and Sorted Sets).
However there is a way for the user to tune the order of magnitude of the number of returned elements per call using the COUNT option.
The COUNT option
While SCAN does not provide guarantees about the number of elements returned at every iteration, it is possible to empirically adjust the behavior of SCAN using the COUNT option. Basically with COUNT the user specifies the amount of work that should be done at every call in order to retrieve elements from the collection. This is just a hint for the implementation, however generally speaking this is what you could expect most of the times from the implementation.
- The default
COUNTvalue is 10. - When iterating the key space, or a Set, Hash or Sorted Set that is big enough to be represented by a hash table, assuming no MATCH option is used, the server will usually return count or a few more than count elements per call. Please check the why SCAN may return all the elements at once section later in this document.
- When iterating Sets encoded as intsets (small sets composed of just integers), or Hashes and Sorted Sets encoded as ziplists (small hashes and sets composed of small individual values), usually all the elements are returned in the first
SCANcall regardless of theCOUNTvalue.
Important: there is no need to use the same COUNT value for every iteration. The caller is free to change the count from one iteration to the other as required, as long as the cursor passed in the next call is the one obtained in the previous call to the command.
The MATCH option
It is possible to only iterate elements matching a given glob-style pattern, similarly to the behavior of the KEYS command that takes a pattern as its only argument.
To do so, just append the MATCH <pattern> arguments at the end of the SCAN command (it works with all the SCAN family commands).
This is an example of iteration using MATCH:
> sadd myset 1 2 3 foo foobar feelsgood (integer) 6 > sscan myset 0 match f* 1) "0" 2) 1) "foo" 2) "feelsgood" 3) "foobar"
res = r.sadd("myset", *set([1, 2, 3, "foo", "foobar", "feelsgood"]))
print(res)
# >>> 6
res = list(r.sscan_iter("myset", match="f*"))
print(res)
# >>> ['foobar', 'foo', 'feelsgood']
import redis
r = redis.Redis(decode_responses=True)
res = r.set("key1", "Hello")
print(res)
# >>> True
res = r.set("key2", "World")
print(res)
# >>> True
res = r.delete("key1", "key2", "key3")
print(res)
# >>> 2
res = r.set("key1", "Hello")
print(res)
# >>> True
res = r.exists("key1")
print(res)
# >>> 1
res = r.exists("nosuchkey")
print(res)
# >>> 0
res = r.set("key2", "World")
print(res)
# >>> True
res = r.exists("key1", "key2", "nosuchkey")
print(res)
# >>> 2
res = r.set("mykey", "Hello")
print(res)
# >>> True
res = r.expire("mykey", 10)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.set("mykey", "Hello World")
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> -1
res = r.expire("mykey", 10, xx=True)
print(res)
# >>> False
res = r.ttl("mykey")
print(res)
# >>> -1
res = r.expire("mykey", 10, nx=True)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.set("mykey", "Hello")
print(res)
# >>> True
res = r.expire("mykey", 10)
print(res)
# >>> True
res = r.ttl("mykey")
print(res)
# >>> 10
res = r.mset({"firstname": "Jack", "lastname": "Stuntman", "age": "35"})
print(res)
# >>> True
res = r.keys("*name*")
print(sorted(res))
# >>> ['firstname', 'lastname']
res = r.keys("a??")
print(res)
# >>> ['age']
res = r.keys("*")
print(sorted(res))
# >>> ['age', 'firstname', 'lastname']
res = r.sadd("myset", *set([1, 2, 3, "foo", "foobar", "feelsgood"]))
print(res)
# >>> 6
res = list(r.sscan_iter("myset", match="f*"))
print(res)
# >>> ['foobar', 'foo', 'feelsgood']
total = 0
cursor, keys = r.scan(cursor=0, 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*')
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
res = r.geoadd("geokey", (0, 0, "value"))
print(res)
# >>> 1
res = r.zadd("zkey", {"value": 1000})
print(res)
# >>> 1
res = r.type("geokey")
print(res)
# >>> zset
res = r.type("zkey")
print(res)
# >>> zset
# 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']
res = r.hset("myhash", mapping={"a": 1, "b": 2})
print(res)
# >>> 2
cursor, keys = r.hscan("myhash", 0)
print(keys)
# >>> {'a': '1', 'b': '2'}
cursor, keys = r.hscan("myhash", 0, no_values=True)
print(sorted(keys))
# >>> ['a', 'b']
const scan1Res1 = await client.sAdd('myset', ['1', '2', '3', 'foo', 'foobar', 'feelsgood']);
console.log(scan1Res1); // 6
let scan1Res2 = [];
for await (const values of client.sScanIterator('myset', { MATCH: 'f*' })) {
scan1Res2 = scan1Res2.concat(values);
}
console.log(scan1Res2); // ['foo', 'foobar', 'feelsgood']
import { createClient } from 'redis';
const client = createClient();
await client.connect().catch(console.error);
const delRes1 = await client.set('key1', 'Hello');
console.log(delRes1); // OK
const delRes2 = await client.set('key2', 'World');
console.log(delRes2); // OK
const delRes3 = await client.del(['key1', 'key2', 'key3']);
console.log(delRes3); // 2
const existsRes1 = await client.set('key1', 'Hello');
console.log(existsRes1); // OK
const existsRes2 = await client.exists('key1');
console.log(existsRes2); // 1
const existsRes3 = await client.exists('nosuchkey');
console.log(existsRes3); // 0
const existsRes4 = await client.set('key2', 'World');
console.log(existsRes4); // OK
const existsRes5 = await client.exists(['key1', 'key2', 'nosuchkey']);
console.log(existsRes5); // 2
const expireRes1 = await client.set('mykey', 'Hello');
console.log(expireRes1); // OK
const expireRes2 = await client.expire('mykey', 10);
console.log(expireRes2); // 1
const expireRes3 = await client.ttl('mykey');
console.log(expireRes3); // 10
const expireRes4 = await client.set('mykey', 'Hello World');
console.log(expireRes4); // OK
const expireRes5 = await client.ttl('mykey');
console.log(expireRes5); // -1
const expireRes6 = await client.expire('mykey', 10, "XX");
console.log(expireRes6); // 0
const expireRes7 = await client.ttl('mykey');
console.log(expireRes7); // -1
const expireRes8 = await client.expire('mykey', 10, "NX");
console.log(expireRes8); // 1
const expireRes9 = await client.ttl('mykey');
console.log(expireRes9); // 10
const ttlRes1 = await client.set('mykey', 'Hello');
console.log(ttlRes1); // OK
const ttlRes2 = await client.expire('mykey', 10);
console.log(ttlRes2); // 1
const ttlRes3 = await client.ttl('mykey');
console.log(ttlRes3); // 10
const keysRes1 = await client.mSet({ firstname: 'Jack', lastname: 'Stuntman', age: '35' });
console.log(keysRes1); // OK
const keysRes2 = await client.keys('*name*');
console.log(keysRes2.sort()); // ['firstname', 'lastname']
const keysRes3 = await client.keys('a??');
console.log(keysRes3); // ['age']
const keysRes4 = await client.keys('*');
console.log(keysRes4.sort()); // ['age', 'firstname', 'lastname']
const scan1Res1 = await client.sAdd('myset', ['1', '2', '3', 'foo', 'foobar', 'feelsgood']);
console.log(scan1Res1); // 6
let scan1Res2 = [];
for await (const values of client.sScanIterator('myset', { MATCH: 'f*' })) {
scan1Res2 = scan1Res2.concat(values);
}
console.log(scan1Res2); // ['foo', 'foobar', 'feelsgood']
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
const scan3Res1 = await client.geoAdd('geokey', { longitude: 0, latitude: 0, member: 'value' });
console.log(scan3Res1); // 1
const scan3Res2 = await client.zAdd('zkey', [{ score: 1000, value: 'value' }]);
console.log(scan3Res2); // 1
const scan3Res3 = await client.type('geokey');
console.log(scan3Res3); // zset
const scan3Res4 = await client.type('zkey');
console.log(scan3Res4); // 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 {
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']
const scan4Res1 = await client.hSet('myhash', { a: 1, b: 2 });
console.log(scan4Res1); // 2
// HSCAN doesn't promise a field order, so pair entries into an object rather than
// relying on position.
const scan4Res2 = await client.hScan('myhash', '0');
const scan4Pairs = Object.fromEntries(scan4Res2.entries.map((e) => [e.field, e.value]));
console.log(scan4Pairs); // {a: '1', b: '2'}
const scan4Res3 = await client.hScan('myhash', '0', { COUNT: 10 });
const items = scan4Res3.entries.map((item) => item.field).sort()
console.log(items); // ['a', 'b']
await client.close();
const scan1Res1 = await redis.sadd('myset', '1', '2', '3', 'foo', 'foobar', 'feelsgood');
console.log(scan1Res1); // >>> 6
const [, scan1Members] = await redis.sscan('myset', 0, 'MATCH', 'f*');
console.log(scan1Members.sort()); // >>> ['feelsgood', 'foo', 'foobar']
import assert from 'node:assert';
import { Redis } from 'ioredis';
const redis = new Redis();
const keysRes1 = await redis.mset({ firstname: 'Jack', lastname: 'Stuntman', age: '35' });
console.log(keysRes1); // >>> OK
const keysRes2 = await redis.keys('*name*');
console.log(keysRes2.sort()); // >>> ['firstname', 'lastname']
const keysRes3 = await redis.keys('a??');
console.log(keysRes3); // >>> ['age']
const keysRes4 = await redis.keys('*');
console.log(keysRes4.sort()); // >>> ['age', 'firstname', 'lastname']
const scan1Res1 = await redis.sadd('myset', '1', '2', '3', 'foo', 'foobar', 'feelsgood');
console.log(scan1Res1); // >>> 6
const [, scan1Members] = await redis.sscan('myset', 0, 'MATCH', 'f*');
console.log(scan1Members.sort()); // >>> ['feelsgood', 'foo', 'foobar']
// 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);
scan2Total += scan2Keys.length;
console.log(scan2Keys.length);
// The per-call split isn't guaranteed, but the cumulative total is.
console.log(scan2Total); // >>> 19
const scan3Res1 = await redis.geoadd('geokey', '0', '0', 'value');
console.log(scan3Res1); // >>> 1
const scan3Res2 = await redis.zadd('zkey', '1000', 'value');
console.log(scan3Res2); // >>> 1
console.log(await redis.type('geokey')); // >>> zset
console.log(await redis.type('zkey')); // >>> 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']
const scan4Res1 = await redis.hset('myhash', { a: 1, b: 2 });
console.log(scan4Res1); // >>> 2
// HSCAN returns field and value interleaved. Redis does not promise an order, so pair
// them up into an object rather than relying on the position of each element.
const [, scan4Flat] = await redis.hscan('myhash', 0);
const scan4Pairs = Object.fromEntries(
scan4Flat.reduce((acc, v, i) => (i % 2 ? acc : [...acc, [v, scan4Flat[i + 1]]]), [])
);
console.log(scan4Pairs); // >>> { a: '1', b: '2' }
const [, scan4Fields] = await redis.hscan('myhash', 0, 'NOVALUES');
console.log(scan4Fields.sort()); // >>> [ 'a', 'b' ]
console.log(await redis.set('key1', 'Hello')); // >>> OK
console.log(await redis.set('key2', 'World')); // >>> OK
const delResult = await redis.del('key1', 'key2', 'key3');
console.log(delResult); // >>> 2
console.log(await redis.set('key1', 'Hello')); // >>> OK
console.log(await redis.exists('key1')); // >>> 1
console.log(await redis.exists('nosuchkey')); // >>> 0
console.log(await redis.set('key2', 'World')); // >>> OK
const existsResult = await redis.exists('key1', 'key2', 'nosuchkey');
console.log(existsResult); // >>> 2
console.log(await redis.set('mykey', 'Hello')); // >>> OK
console.log(await redis.expire('mykey', 10)); // >>> 1
console.log(await redis.ttl('mykey')); // >>> 10
// Overwriting a key with SET clears its expiry.
console.log(await redis.set('mykey', 'Hello World')); // >>> OK
console.log(await redis.ttl('mykey')); // >>> -1
// XX only sets the expiry when one already exists, so this is a no-op.
console.log(await redis.expire('mykey', 10, 'XX')); // >>> 0
console.log(await redis.ttl('mykey')); // >>> -1
// NX only sets the expiry when there is none, so this one applies.
console.log(await redis.expire('mykey', 10, 'NX')); // >>> 1
const expireTtl = await redis.ttl('mykey');
console.log(expireTtl); // >>> 10
console.log(await redis.set('mykey', 'Hello')); // >>> OK
console.log(await redis.expire('mykey', 10)); // >>> 1
const ttlResult = await redis.ttl('mykey');
console.log(ttlResult); // >>> 10
redis.disconnect();
long scan1Result1 = jedis.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood");
System.out.println(scan1Result1); // >>> 6
ScanResult<String> scan1Result2 = jedis.sscan(
"myset", "0", new ScanParams().match("f*")
);
ArrayList<String> scan1Members = new ArrayList<>(scan1Result2.getResult());
Collections.sort(scan1Members);
System.out.println(scan1Members); // >>> [feelsgood, foo, foobar]
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.args.ExpiryOption;
import redis.clients.jedis.params.ScanParams;
import redis.clients.jedis.resps.ScanResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CmdsGenericExample {
public void run() {
RedisClient jedis = RedisClient.create("redis://localhost:6379");
String delResult1 = jedis.set("key1", "Hello");
System.out.println(delResult1); // >>> OK
String delResult2 = jedis.set("key2", "World");
System.out.println(delResult2); // >>> OK
long delResult3 = jedis.del("key1", "key2", "key3");
System.out.println(delResult3); // >>> 2
// Tests for 'del' step.
String existsResult1 = jedis.set("key1", "Hello");
System.out.println(existsResult1); // >>> OK
boolean existsResult2 = jedis.exists("key1");
System.out.println(existsResult2); // >>> true
boolean existsResult3 = jedis.exists("nosuchkey");
System.out.println(existsResult3); // >>> false
String existsResult4 = jedis.set("key2", "World");
System.out.println(existsResult4); // >>> OK
long existsResult5 = jedis.exists("key1", "key2", "nosuchkey");
System.out.println(existsResult5); // >>> 2
// Tests for 'exists' step.
String expireResult1 = jedis.set("mykey", "Hello");
System.out.println(expireResult1); // >>> OK
long expireResult2 = jedis.expire("mykey", 10);
System.out.println(expireResult2); // >>> 1
long expireResult3 = jedis.ttl("mykey");
System.out.println(expireResult3); // >>> 10
String expireResult4 = jedis.set("mykey", "Hello World");
System.out.println(expireResult4); // >>> OK
long expireResult5 = jedis.ttl("mykey");
System.out.println(expireResult5); // >>> -1
long expireResult6 = jedis.expire("mykey", 10, ExpiryOption.XX);
System.out.println(expireResult6); // >>> 0
long expireResult7 = jedis.ttl("mykey");
System.out.println(expireResult7); // >>> -1
long expireResult8 = jedis.expire("mykey", 10, ExpiryOption.NX);
System.out.println(expireResult8); // >>> 1
long expireResult9 = jedis.ttl("mykey");
System.out.println(expireResult9); // >>> 10
// Tests for 'expire' step.
String ttlResult1 = jedis.set("mykey", "Hello");
System.out.println(ttlResult1); // >>> OK
long ttlResult2 = jedis.expire("mykey", 10);
System.out.println(ttlResult2); // >>> 1
long ttlResult3 = jedis.ttl("mykey");
System.out.println(ttlResult3); // >>> 10
// Tests for 'ttl' step.
String keysResult1 = jedis.mset("firstname", "Jack", "lastname", "Stuntman", "age", "35");
System.out.println(keysResult1); // >>> OK
Set<String> keysResult2 = jedis.keys("*name*");
ArrayList<String> keysResult2List = new ArrayList<>(keysResult2);
Collections.sort(keysResult2List);
System.out.println(keysResult2List); // >>> [firstname, lastname]
Set<String> keysResult3 = jedis.keys("a??");
System.out.println(keysResult3); // >>> [age]
Set<String> keysResult4 = jedis.keys("*");
ArrayList<String> keysResult4List = new ArrayList<>(keysResult4);
Collections.sort(keysResult4List);
System.out.println(keysResult4List); // >>> [age, firstname, lastname]
// Tests for 'keys' step.
long scan1Result1 = jedis.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood");
System.out.println(scan1Result1); // >>> 6
ScanResult<String> scan1Result2 = jedis.sscan(
"myset", "0", new ScanParams().match("f*")
);
ArrayList<String> scan1Members = new ArrayList<>(scan1Result2.getResult());
Collections.sort(scan1Members);
System.out.println(scan1Members); // >>> [feelsgood, foo, foobar]
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
String scan2Cursor = "0";
ScanResult<String> 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));
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
long scan3Result1 = jedis.geoadd("geokey", 0, 0, "value");
System.out.println(scan3Result1); // >>> 1
long scan3Result2 = jedis.zadd("zkey", 1000, "value");
System.out.println(scan3Result2); // >>> 1
System.out.println(jedis.type("geokey")); // >>> zset
System.out.println(jedis.type("zkey")); // >>> zset
// 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<String> scan3Keys = new ArrayList<>();
do {
ScanResult<String> 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]
long scan4Result1 = jedis.hset("myhash", Map.of("a", "1", "b", "2"));
System.out.println(scan4Result1); // >>> 2
ScanResult<Map.Entry<String, String>> scan4Result2 = jedis.hscan(
"myhash", "0", new ScanParams()
);
ArrayList<String> scan4Pairs = new ArrayList<>();
for (Map.Entry<String, String> entry : scan4Result2.getResult()) {
scan4Pairs.add(entry.getKey() + "=" + entry.getValue());
}
Collections.sort(scan4Pairs);
System.out.println(scan4Pairs); // >>> [a=1, b=2]
ScanResult<String> scan4Result3 = jedis.hscanNoValues(
"myhash", "0", new ScanParams()
);
ArrayList<String> scan4Fields = new ArrayList<>(scan4Result3.getResult());
Collections.sort(scan4Fields);
System.out.println(scan4Fields); // >>> [a, b]
jedis.close();
}
}
CompletableFuture<Void> scan1Example = asyncCommands
.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood")
.thenCompose(scan1Res1 -> {
System.out.println(scan1Res1); // >>> 6
return asyncCommands.sscan("myset", ScanArgs.Builder.matches("f*"));
})
.thenAccept(scan1Res2 -> {
List<String> members = new java.util.ArrayList<>(scan1Res2.getValues());
Collections.sort(members);
System.out.println(members); // >>> [feelsgood, foo, foobar]
})
.toCompletableFuture();
package io.redis.examples.async;
import io.lettuce.core.*;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.StatefulRedisConnection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class CmdsGenericExample {
public void run() {
CompletableFuture<Void> existsExample = asyncCommands.set("key1", "Hello").thenCompose(res1 -> {
System.out.println(res1); // >>> OK
return asyncCommands.exists("key1");
}).thenCompose(res2 -> {
System.out.println(res2); // >>> 1
return asyncCommands.exists("nosuchkey");
}).thenCompose(res3 -> {
System.out.println(res3); // >>> 0
return asyncCommands.set("key2", "World");
}).thenCompose(res4 -> {
System.out.println(res4); // >>> OK
return asyncCommands.exists("key1", "key2", "nosuchkey");
}).thenAccept(res5 -> {
System.out.println(res5); // >>> 2
}).toCompletableFuture();
existsExample.join();
CompletableFuture<Void> keysExample = asyncCommands.mset(Map.of(
"firstname", "Jack",
"lastname", "Stuntman",
"age", "35"
)).thenCompose(res1 -> {
System.out.println(res1); // >>> OK
return asyncCommands.keys("*name*");
}).thenCompose(res2 -> {
Collections.sort(res2);
System.out.println(res2); // >>> [firstname, lastname]
return asyncCommands.keys("a??");
}).thenCompose(res3 -> {
System.out.println(res3); // >>> [age]
return asyncCommands.keys("*");
}).thenAccept(res4 -> {
Collections.sort(res4);
System.out.println(res4); // >>> [age, firstname, lastname]
}).toCompletableFuture();
keysExample.join();
CompletableFuture<Void> scan1Example = asyncCommands
.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood")
.thenCompose(scan1Res1 -> {
System.out.println(scan1Res1); // >>> 6
return asyncCommands.sscan("myset", ScanArgs.Builder.matches("f*"));
})
.thenAccept(scan1Res2 -> {
List<String> members = new java.util.ArrayList<>(scan1Res2.getValues());
Collections.sort(members);
System.out.println(members); // >>> [feelsgood, foo, foobar]
})
.toCompletableFuture();
scan1Example.join();
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all. Each iteration is awaited because
// the next one needs the cursor this one returns.
KeyScanCursor<String> 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());
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Cursor = asyncCommands
.scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000))
.toCompletableFuture().join();
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
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<String> scan3Keys = new java.util.ArrayList<>();
KeyScanCursor<String> 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]
CompletableFuture<Void> scan4Example = asyncCommands
.hset("myhash", Map.of("a", "1", "b", "2"))
.thenCompose(scan4Res1 -> {
System.out.println(scan4Res1); // >>> 2
return asyncCommands.hscan("myhash");
})
.thenCompose(scan4Res2 -> {
System.out.println(new java.util.TreeMap<>(scan4Res2.getMap()));
// >>> {a=1, b=2}
return asyncCommands.hscanNovalues("myhash");
})
.thenAccept(scan4Res3 -> {
List<String> fields = new java.util.ArrayList<>(scan4Res3.getKeys());
Collections.sort(fields);
System.out.println(fields); // >>> [a, b]
})
.toCompletableFuture();
scan4Example.join();
CompletableFuture<Void> delExample = asyncCommands.set("key1", "Hello")
.thenCompose(r1 -> {
System.out.println(r1); // >>> OK
return asyncCommands.set("key2", "World");
})
.thenCompose(r2 -> {
System.out.println(r2); // >>> OK
return asyncCommands.del("key1", "key2", "key3");
})
.thenAccept(r3 -> {
System.out.println(r3); // >>> 2
})
.toCompletableFuture();
delExample.join();
CompletableFuture<Void> expireExample = asyncCommands.set("mykey", "Hello")
.thenCompose(r1 -> {
System.out.println(r1); // >>> OK
return asyncCommands.expire("mykey", 10);
})
.thenCompose(r2 -> {
System.out.println(r2); // >>> true
return asyncCommands.ttl("mykey");
})
.thenCompose(r3 -> {
System.out.println(r3); // >>> 10
// Overwriting a key with SET clears its expiry.
return asyncCommands.set("mykey", "Hello World");
})
.thenCompose(r4 -> {
System.out.println(r4); // >>> OK
return asyncCommands.ttl("mykey");
})
.thenCompose(r5 -> {
System.out.println(r5); // >>> -1
// XX only sets the expiry when one already exists, so this is a no-op.
return asyncCommands.expire("mykey", 10, ExpireArgs.Builder.xx());
})
.thenCompose(r6 -> {
System.out.println(r6); // >>> false
return asyncCommands.ttl("mykey");
})
.thenCompose(r7 -> {
System.out.println(r7); // >>> -1
// NX only sets the expiry when there is none, so this one applies.
return asyncCommands.expire("mykey", 10, ExpireArgs.Builder.nx());
})
.thenCompose(r8 -> {
System.out.println(r8); // >>> true
return asyncCommands.ttl("mykey");
})
.thenAccept(r9 -> {
System.out.println(r9); // >>> 10
})
.toCompletableFuture();
expireExample.join();
CompletableFuture<Void> ttlExample = asyncCommands.set("mykey", "Hello")
.thenCompose(r1 -> {
System.out.println(r1); // >>> OK
return asyncCommands.expire("mykey", 10);
})
.thenCompose(r2 -> {
System.out.println(r2); // >>> true
return asyncCommands.ttl("mykey");
})
.thenAccept(r3 -> {
System.out.println(r3); // >>> 10
})
.toCompletableFuture();
ttlExample.join();
} finally {
redisClient.shutdown();
}
}
}
Mono<Void> scan1Example = reactiveCommands
.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood")
.flatMap(scan1Res1 -> {
System.out.println(scan1Res1); // >>> 6
return reactiveCommands.sscan("myset", ScanArgs.Builder.matches("f*"));
})
.doOnNext(scan1Res2 -> {
List<String> members = new java.util.ArrayList<>(scan1Res2.getValues());
Collections.sort(members);
System.out.println(members); // >>> [feelsgood, foo, foobar]
})
.then();
package io.redis.examples.reactive;
import io.lettuce.core.*;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.StatefulRedisConnection;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class CmdsGenericExample {
public void run() {
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();
Mono<Void> existsExample = reactiveCommands.set("key1", "Hello").doOnNext(res1 -> {
System.out.println(res1); // >>> OK
}).then(reactiveCommands.exists("key1")).doOnNext(res2 -> {
System.out.println(res2); // >>> 1
}).then(reactiveCommands.exists("nosuchkey")).doOnNext(res3 -> {
System.out.println(res3); // >>> 0
}).then(reactiveCommands.set("key2", "World")).doOnNext(res4 -> {
System.out.println(res4); // >>> OK
}).then(reactiveCommands.exists("key1", "key2", "nosuchkey")).doOnNext(res5 -> {
System.out.println(res5); // >>> 2
}).then();
Mono.when(existsExample).block();
Mono<Void> keysExample = reactiveCommands.mset(Map.of(
"firstname", "Jack",
"lastname", "Stuntman",
"age", "35"
)).doOnNext(res1 -> {
System.out.println(res1); // >>> OK
}).then(reactiveCommands.keys("*name*").collectList()).doOnNext(res2 -> {
Collections.sort(res2);
System.out.println(res2); // >>> [firstname, lastname]
}).then(reactiveCommands.keys("a??").collectList()).doOnNext(res3 -> {
System.out.println(res3); // >>> [age]
}).then(reactiveCommands.keys("*").collectList()).doOnNext(res4 -> {
Collections.sort(res4);
System.out.println(res4); // >>> [age, firstname, lastname]
}).then();
Mono.when(keysExample).block();
Mono<Void> scan1Example = reactiveCommands
.sadd("myset", "1", "2", "3", "foo", "foobar", "feelsgood")
.flatMap(scan1Res1 -> {
System.out.println(scan1Res1); // >>> 6
return reactiveCommands.sscan("myset", ScanArgs.Builder.matches("f*"));
})
.doOnNext(scan1Res2 -> {
List<String> members = new java.util.ArrayList<>(scan1Res2.getValues());
Collections.sort(members);
System.out.println(members); // >>> [feelsgood, foo, foobar]
})
.then();
Mono.when(scan1Example).block();
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all. Each iteration is subscribed to in
// turn because the next one needs the cursor this one returns.
KeyScanCursor<String> 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());
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Cursor = reactiveCommands
.scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000)).block();
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
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<String> scan3Keys = new java.util.ArrayList<>();
KeyScanCursor<String> 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]
Mono<Void> scan4Example = reactiveCommands
.hset("myhash", Map.of("a", "1", "b", "2"))
.flatMap(scan4Res1 -> {
System.out.println(scan4Res1); // >>> 2
return reactiveCommands.hscan("myhash");
})
.flatMap(scan4Res2 -> {
System.out.println(new java.util.TreeMap<>(scan4Res2.getMap()));
// >>> {a=1, b=2}
return reactiveCommands.hscanNovalues("myhash");
})
.doOnNext(scan4Res3 -> {
List<String> fields = new java.util.ArrayList<>(scan4Res3.getKeys());
Collections.sort(fields);
System.out.println(fields); // >>> [a, b]
})
.then();
Mono.when(scan4Example).block();
Mono<Void> delExample = reactiveCommands.set("key1", "Hello")
.flatMap(r1 -> {
System.out.println(r1); // >>> OK
return reactiveCommands.set("key2", "World");
})
.flatMap(r2 -> {
System.out.println(r2); // >>> OK
return reactiveCommands.del("key1", "key2", "key3");
})
.doOnNext(r3 -> {
System.out.println(r3); // >>> 2
})
.then();
Mono.when(delExample).block();
Mono<Void> expireExample = reactiveCommands.set("mykey", "Hello")
.flatMap(r1 -> {
System.out.println(r1); // >>> OK
return reactiveCommands.expire("mykey", 10);
})
.flatMap(r2 -> {
System.out.println(r2); // >>> true
return reactiveCommands.ttl("mykey");
})
.flatMap(r3 -> {
System.out.println(r3); // >>> 10
// Overwriting a key with SET clears its expiry.
return reactiveCommands.set("mykey", "Hello World");
})
.flatMap(r4 -> {
System.out.println(r4); // >>> OK
return reactiveCommands.ttl("mykey");
})
.flatMap(r5 -> {
System.out.println(r5); // >>> -1
// XX only sets the expiry when one already exists, so this is a no-op.
return reactiveCommands.expire("mykey", 10, ExpireArgs.Builder.xx());
})
.flatMap(r6 -> {
System.out.println(r6); // >>> false
return reactiveCommands.ttl("mykey");
})
.flatMap(r7 -> {
System.out.println(r7); // >>> -1
// NX only sets the expiry when there is none, so this one applies.
return reactiveCommands.expire("mykey", 10, ExpireArgs.Builder.nx());
})
.flatMap(r8 -> {
System.out.println(r8); // >>> true
return reactiveCommands.ttl("mykey");
})
.doOnNext(r9 -> {
System.out.println(r9); // >>> 10
})
.then();
Mono.when(expireExample).block();
Mono<Void> ttlExample = reactiveCommands.set("mykey", "Hello")
.flatMap(r1 -> {
System.out.println(r1); // >>> OK
return reactiveCommands.expire("mykey", 10);
})
.flatMap(r2 -> {
System.out.println(r2); // >>> true
return reactiveCommands.ttl("mykey");
})
.doOnNext(r3 -> {
System.out.println(r3); // >>> 10
})
.then();
Mono.when(ttlExample).block();
} finally {
redisClient.shutdown();
}
}
}
scan1Result1, err := rdb.SAdd(ctx, "myset", "1", "2", "3", "foo", "foobar", "feelsgood").Result()
if err != nil {
panic(err)
}
fmt.Println(scan1Result1) // >>> 6
scan1Result2, _, err := rdb.SScan(ctx, "myset", 0, "f*", 0).Result()
if err != nil {
panic(err)
}
sort.Strings(scan1Result2)
fmt.Println(scan1Result2) // >>> [feelsgood foo foobar]
package example_commands_test
import (
"context"
"fmt"
"math"
"sort"
"time"
"github.com/redis/go-redis/v9"
)
func ExampleClient_del_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
delResult1, err := rdb.Set(ctx, "key1", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(delResult1) // >>> OK
delResult2, err := rdb.Set(ctx, "key2", "World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(delResult2) // >>> OK
delResult3, err := rdb.Del(ctx, "key1", "key2", "key3").Result()
if err != nil {
panic(err)
}
fmt.Println(delResult3) // >>> 2
}
func ExampleClient_exists_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
existsResult1, err := rdb.Set(ctx, "key1", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult1) // >>> OK
existsResult2, err := rdb.Exists(ctx, "key1").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult2) // >>> 1
existsResult3, err := rdb.Exists(ctx, "nosuchkey").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult3) // >>> 0
existsResult4, err := rdb.Set(ctx, "key2", "World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult4) // >>> OK
existsResult5, err := rdb.Exists(ctx, "key1", "key2", "nosuchkey").Result()
if err != nil {
panic(err)
}
fmt.Println(existsResult5) // >>> 2
}
func ExampleClient_expire_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
expireResult1, err := rdb.Set(ctx, "mykey", "Hello", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult1) // >>> OK
expireResult2, err := rdb.Expire(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult2) // >>> true
expireResult3, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(expireResult3.Seconds())) // >>> 10
expireResult4, err := rdb.Set(ctx, "mykey", "Hello World", 0).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult4) // >>> OK
expireResult5, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult5) // >>> -1ns
expireResult6, err := rdb.ExpireXX(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult6) // >>> false
expireResult7, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult7) // >>> -1ns
expireResult8, err := rdb.ExpireNX(ctx, "mykey", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(expireResult8) // >>> true
expireResult9, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(expireResult9.Seconds())) // >>> 10
}
func ExampleClient_keys_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
keysResult1, err := rdb.MSet(ctx, "firstname", "Jack", "lastname", "Stuntman", "age", "35").Result()
if err != nil {
panic(err)
}
fmt.Println(keysResult1) // >>> OK
keysResult2, err := rdb.Keys(ctx, "*name*").Result()
if err != nil {
panic(err)
}
sort.Strings(keysResult2)
fmt.Println(keysResult2) // >>> [firstname lastname]
keysResult3, err := rdb.Keys(ctx, "a??").Result()
if err != nil {
panic(err)
}
fmt.Println(keysResult3) // >>> [age]
keysResult4, err := rdb.Keys(ctx, "*").Result()
if err != nil {
panic(err)
}
sort.Strings(keysResult4)
fmt.Println(keysResult4) // >>> [age firstname lastname]
}
func ExampleClient_ttl_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
ttlResult1, err := rdb.Set(ctx, "mykey", "Hello", 10*time.Second).Result()
if err != nil {
panic(err)
}
fmt.Println(ttlResult1) // >>> OK
ttlResult2, err := rdb.TTL(ctx, "mykey").Result()
if err != nil {
panic(err)
}
fmt.Println(math.Round(ttlResult2.Seconds())) // >>> 10
}
func ExampleClient_scan1_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
scan1Result1, err := rdb.SAdd(ctx, "myset", "1", "2", "3", "foo", "foobar", "feelsgood").Result()
if err != nil {
panic(err)
}
fmt.Println(scan1Result1) // >>> 6
scan1Result2, _, err := rdb.SScan(ctx, "myset", 0, "f*", 0).Result()
if err != nil {
panic(err)
}
sort.Strings(scan1Result2)
fmt.Println(scan1Result2) // >>> [feelsgood foo foobar]
}
func ExampleClient_scan2_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
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()
if err != nil {
panic(err)
}
scan2Total += len(scan2Keys)
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Keys, _, err = rdb.Scan(ctx, scan2Cursor, "*11*", 1000).Result()
if err != nil {
panic(err)
}
scan2Total += len(scan2Keys)
// The per-call split isn't guaranteed, but the cumulative total is.
fmt.Println(scan2Total) // >>> 19
}
func ExampleClient_scan3_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
scan3Result1, err := rdb.GeoAdd(ctx, "geokey", &redis.GeoLocation{
Longitude: 0, Latitude: 0, Name: "value",
}).Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result1) // >>> 1
scan3Result2, err := rdb.ZAdd(ctx, "zkey", redis.Z{Score: 1000, Member: "value"}).Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result2) // >>> 1
scan3Result3, err := rdb.Type(ctx, "geokey").Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result3) // >>> zset
scan3Result4, err := rdb.Type(ctx, "zkey").Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result4) // >>> zset
// 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
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(scan3Keys)
fmt.Println(scan3Keys) // >>> [geokey zkey]
}
func ExampleClient_scan4_cmd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
scan4Result1, err := rdb.HSet(ctx, "myhash", "a", 1, "b", 2).Result()
if err != nil {
panic(err)
}
fmt.Println(scan4Result1) // >>> 2
scan4Result2, _, err := rdb.HScan(ctx, "myhash", 0, "", 0).Result()
if err != nil {
panic(err)
}
// HSCAN returns field and value interleaved. Redis does not promise an order, so
// collect the pairs into a map: fmt prints map keys sorted, whatever order they arrived in.
scan4Fields := map[string]string{}
for i := 0; i < len(scan4Result2); i += 2 {
scan4Fields[scan4Result2[i]] = scan4Result2[i+1]
}
fmt.Println(scan4Fields) // >>> map[a:1 b:2]
scan4Result3, _, err := rdb.HScanNoValues(ctx, "myhash", 0, "", 0).Result()
if err != nil {
panic(err)
}
sort.Strings(scan4Result3)
fmt.Println(scan4Result3) // >>> [a b]
}
reply = redisCommand(c, "SADD myset 1 2 3 foo foobar feelsgood");
printf("%lld\n", reply->integer);
// >>> 6
freeReplyObject(reply);
// SCAN-family replies are a two-element array: the next cursor, then the results.
reply = redisCommand(c, "SSCAN myset 0 MATCH f*");
printf("%zu\n", reply->element[1]->elements);
// >>> 3
freeReplyObject(reply);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>
int main(int argc, char **argv) {
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
return 1;
}
redisReply *reply;
// Set up keys
reply = redisCommand(c, "MSET %s %s %s %s %s %s",
"firstname", "Jack", "lastname", "Stuntman", "age", "35");
printf("MSET firstname Jack lastname Stuntman age 35: %s\n", reply->str);
// >>> OK
freeReplyObject(reply);
// Keys matching *name*
reply = redisCommand(c, "KEYS %s", "*name*");
printf("KEYS *name*:\n");
for (size_t i = 0; i < reply->elements; i++) {
printf(" %s\n", reply->element[i]->str);
}
// >>> firstname
// >>> lastname
freeReplyObject(reply);
// Keys matching a??
reply = redisCommand(c, "KEYS %s", "a??");
printf("KEYS a??:\n");
for (size_t i = 0; i < reply->elements; i++) {
printf(" %s\n", reply->element[i]->str);
}
// >>> age
freeReplyObject(reply);
// All keys
reply = redisCommand(c, "KEYS %s", "*");
printf("KEYS *:\n");
for (size_t i = 0; i < reply->elements; i++) {
printf(" %s\n", reply->element[i]->str);
}
// >>> age
// >>> firstname
// >>> lastname
freeReplyObject(reply);
reply = redisCommand(c, "SADD myset 1 2 3 foo foobar feelsgood");
printf("%lld\n", reply->integer);
// >>> 6
freeReplyObject(reply);
// SCAN-family replies are a two-element array: the next cursor, then the results.
reply = redisCommand(c, "SSCAN myset 0 MATCH f*");
printf("%zu\n", reply->element[1]->elements);
// >>> 3
freeReplyObject(reply);
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
char cursor[64] = "0";
for (int i = 0; i < 4; i++) {
reply = redisCommand(c, "SCAN %s MATCH *11*", cursor);
snprintf(cursor, sizeof(cursor), "%s", reply->element[0]->str);
printf("%zu\n", reply->element[1]->elements);
freeReplyObject(reply);
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
reply = redisCommand(c, "SCAN %s MATCH *11* COUNT 1000", cursor);
printf("%zu\n", reply->element[1]->elements);
// >>> 18
freeReplyObject(reply);
reply = redisCommand(c, "GEOADD geokey 0 0 value");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "ZADD zkey 1000 value");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "TYPE geokey");
printf("%s\n", reply->str);
// >>> zset
freeReplyObject(reply);
reply = redisCommand(c, "TYPE zkey");
printf("%s\n", reply->str);
// >>> zset
freeReplyObject(reply);
reply = redisCommand(c, "SCAN 0 TYPE zset");
printf("%zu\n", reply->element[1]->elements);
// >>> 2
freeReplyObject(reply);
reply = redisCommand(c, "HSET myhash a 1 b 2");
printf("%lld\n", reply->integer);
// >>> 2
freeReplyObject(reply);
// Without NOVALUES the results alternate field, value, field, value.
reply = redisCommand(c, "HSCAN myhash 0");
for (size_t i = 0; i < reply->element[1]->elements; i += 2) {
printf("%s=%s\n", reply->element[1]->element[i]->str,
reply->element[1]->element[i + 1]->str);
}
// >>> a=1
// >>> b=2
freeReplyObject(reply);
reply = redisCommand(c, "HSCAN myhash 0 NOVALUES");
for (size_t i = 0; i < reply->element[1]->elements; i++) {
printf("%s\n", reply->element[1]->element[i]->str);
}
// >>> a
// >>> b
freeReplyObject(reply);
reply = redisCommand(c, "SET key1 Hello");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "SET key2 World");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "DEL key1 key2 key3");
printf("%lld\n", reply->integer);
// >>> 2
freeReplyObject(reply);
reply = redisCommand(c, "SET key1 Hello");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "EXISTS key1");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "EXISTS nosuchkey");
printf("%lld\n", reply->integer);
// >>> 0
freeReplyObject(reply);
reply = redisCommand(c, "SET key2 World");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "EXISTS key1 key2 nosuchkey");
printf("%lld\n", reply->integer);
// >>> 2
freeReplyObject(reply);
reply = redisCommand(c, "SET mykey Hello");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "EXPIRE mykey 10");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "TTL mykey");
printf("%lld\n", reply->integer);
// >>> 10
freeReplyObject(reply);
// Overwriting a key with SET clears its expiry.
reply = redisCommand(c, "SET mykey %s", "Hello World");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "TTL mykey");
printf("%lld\n", reply->integer);
// >>> -1
freeReplyObject(reply);
// XX only sets the expiry when one already exists, so this is a no-op.
reply = redisCommand(c, "EXPIRE mykey 10 XX");
printf("%lld\n", reply->integer);
// >>> 0
freeReplyObject(reply);
reply = redisCommand(c, "TTL mykey");
printf("%lld\n", reply->integer);
// >>> -1
freeReplyObject(reply);
// NX only sets the expiry when there is none, so this one applies.
reply = redisCommand(c, "EXPIRE mykey 10 NX");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "TTL mykey");
printf("%lld\n", reply->integer);
// >>> 10
freeReplyObject(reply);
reply = redisCommand(c, "SET mykey Hello");
printf("%s\n", reply->str);
// >>> OK
freeReplyObject(reply);
reply = redisCommand(c, "EXPIRE mykey 10");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "TTL mykey");
printf("%lld\n", reply->integer);
// >>> 10
freeReplyObject(reply);
redisFree(c);
return 0;
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class CmdsGenericExample
{
public void Run()
{
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();
// Tests for 'copy' step.
bool delResult1 = db.StringSet("key1", "Hello");
Console.WriteLine(delResult1); // >>> true
bool delResult2 = db.StringSet("key2", "World");
Console.WriteLine(delResult2); // >>> true
long delResult3 = db.KeyDelete(["key1", "key2", "key3"]);
Console.WriteLine(delResult3); // >>> 2
// Tests for 'del' step.
// Tests for 'dump' step.
bool existsResult1 = db.StringSet("key1", "Hello");
Console.WriteLine(existsResult1); // >>> true
bool existsResult2 = db.KeyExists("key1");
Console.WriteLine(existsResult2); // >>> true
bool existsResult3 = db.KeyExists("nosuchkey");
Console.WriteLine(existsResult3); // >>> false
bool existsResult4 = db.StringSet("key2", "World");
Console.WriteLine(existsResult4); // >>> true
long existsResult5 = db.KeyExists(["key1", "key2", "nosuchkey"]);
Console.WriteLine(existsResult5); // >>> 2
// Tests for 'exists' step.
bool expireResult1 = db.StringSet("mykey", "Hello");
Console.WriteLine(expireResult1); // >>> true
bool expireResult2 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10));
Console.WriteLine(expireResult2); // >>> true
TimeSpan expireResult3 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult3.TotalSeconds)); // >>> 10
bool expireResult4 = db.StringSet("mykey", "Hello World");
Console.WriteLine(expireResult4); // >>> true
TimeSpan expireResult5 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult5.TotalSeconds).ToString()); // >>> 0
bool expireResult6 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasExpiry);
Console.WriteLine(expireResult6); // >>> false
TimeSpan expireResult7 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult7.TotalSeconds)); // >>> 0
bool expireResult8 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasNoExpiry);
Console.WriteLine(expireResult8); // >>> true
TimeSpan expireResult9 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
Console.WriteLine(Math.Round(expireResult9.TotalSeconds)); // >>> 10
// Tests for 'expire' step.
// Tests for 'expireat' step.
// Tests for 'expiretime' step.
bool keysResult1 = db.StringSet(
new KeyValuePair<RedisKey, RedisValue>[] {
new("firstname", "Jack"),
new("lastname", "Stuntman"),
new("age", "35")
}
);
Console.WriteLine(keysResult1); // >>> True
IServer server = muxer.GetServer("localhost:6379");
RedisKey[] keysResult2 = server.Keys(pattern: "*name*").ToArray();
Array.Sort(keysResult2, (a, b) => a.ToString().CompareTo(b.ToString()));
Console.WriteLine(string.Join(", ", keysResult2.Select(k => k.ToString()))); // >>> firstname, lastname
RedisKey[] keysResult3 = server.Keys(pattern: "a??").ToArray();
Console.WriteLine(string.Join(", ", keysResult3.Select(k => k.ToString()))); // >>> age
RedisKey[] keysResult4 = server.Keys(pattern: "*").ToArray();
Array.Sort(keysResult4, (a, b) => a.ToString().CompareTo(b.ToString()));
Console.WriteLine(string.Join(", ", keysResult4.Select(k => k.ToString()))); // >>> age, firstname, lastname
// Tests for 'keys' step.
// Tests for 'migrate' step.
// Tests for 'move' step.
// Tests for 'object_encoding' step.
// Tests for 'object_freq' step.
// Tests for 'object_idletime' step.
// Tests for 'object_refcount' step.
// Tests for 'persist' step.
// Tests for 'pexpire' step.
// Tests for 'pexpireat' step.
// Tests for 'pexpiretime' step.
// Tests for 'pttl' step.
// Tests for 'randomkey' step.
// Tests for 'rename' step.
// Tests for 'renamenx' step.
// Tests for 'restore' step.
// Tests for 'scan1' step.
// Tests for 'scan2' step.
// Tests for 'scan3' step.
// Tests for 'scan4' step.
// Tests for 'sort' step.
// Tests for 'sort_ro' step.
// Tests for 'touch' step.
bool ttlResult1 = db.StringSet("mykey", "Hello");
Console.WriteLine(ttlResult1); // >>> true
bool ttlResult2 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10));
Console.WriteLine(ttlResult2);
TimeSpan ttlResult3 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
string ttlRes = Math.Round(ttlResult3.TotalSeconds).ToString();
Console.WriteLine(Math.Round(ttlResult3.TotalSeconds)); // >>> 10
// Tests for 'ttl' step.
// Tests for 'type' step.
// Tests for 'unlink' step.
// Tests for 'wait' step.
// Tests for 'waitaof' step.
}
}
$scan1Result1 = $r->sadd('myset', ['1', '2', '3', 'foo', 'foobar', 'feelsgood']);
echo $scan1Result1 . PHP_EOL; // >>> 6
[$scan1Cursor, $scan1Members] = $r->sscan('myset', 0, ['MATCH' => 'f*']);
sort($scan1Members);
echo implode(', ', $scan1Members) . PHP_EOL; // >>> feelsgood, foo, foobar
<?php
use PHPUnit\Framework\TestCase;
use Predis\Client as PredisClient;
class CmdsGenericTest
{
public function testCmdsGeneric() {
$r = new PredisClient([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
]);
$existsResult1 = $r->set('key1', 'Hello');
echo $existsResult1 . PHP_EOL; // >>> OK
$existsResult2 = $r->exists('key1');
echo $existsResult2 . PHP_EOL; // >>> 1
$existsResult3 = $r->exists('nosuchkey');
echo $existsResult3 . PHP_EOL; // >>> 0
$existsResult4 = $r->set('key2', 'World');
echo $existsResult4 . PHP_EOL; // >>> OK
$existsResult5 = $r->exists('key1', 'key2', 'nosuchkey');
echo $existsResult5 . PHP_EOL; // >>> 2
$keysResult1 = $r->mset(['firstname' => 'Jack', 'lastname' => 'Stuntman', 'age' => '35']);
echo $keysResult1 . PHP_EOL; // >>> OK
$keysResult2 = $r->keys('*name*');
sort($keysResult2);
echo implode(', ', $keysResult2) . PHP_EOL; // >>> firstname, lastname
$keysResult3 = $r->keys('a??');
echo implode(', ', $keysResult3) . PHP_EOL; // >>> age
$keysResult4 = $r->keys('*');
sort($keysResult4);
echo implode(', ', $keysResult4) . PHP_EOL; // >>> age, firstname, lastname
$scan1Result1 = $r->sadd('myset', ['1', '2', '3', 'foo', 'foobar', 'feelsgood']);
echo $scan1Result1 . PHP_EOL; // >>> 6
[$scan1Cursor, $scan1Members] = $r->sscan('myset', 0, ['MATCH' => 'f*']);
sort($scan1Members);
echo implode(', ', $scan1Members) . PHP_EOL; // >>> feelsgood, foo, foobar
// 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]);
$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
$scan4Result1 = $r->hset('myhash', 'a', 1, 'b', 2);
echo $scan4Result1 . PHP_EOL; // >>> 2
[$scan4Cursor, $scan4Pairs] = $r->hscan('myhash', 0);
echo json_encode($scan4Pairs) . PHP_EOL; // >>> {"a":"1","b":"2"}
// Redis does not promise a field order, so sort before comparing.
[$scan4Cursor, $scan4Fields] = $r->hscan('myhash', 0, ['NOVALUES' => true]);
sort($scan4Fields);
echo implode(', ', $scan4Fields) . PHP_EOL; // >>> a, b
echo $r->set('key1', 'Hello') . PHP_EOL; // >>> OK
echo $r->set('key2', 'World') . PHP_EOL; // >>> OK
$delResult = $r->del('key1', 'key2', 'key3');
echo $delResult . PHP_EOL; // >>> 2
echo $r->set('mykey', 'Hello') . PHP_EOL; // >>> OK
echo $r->expire('mykey', 10) . PHP_EOL; // >>> 1
echo $r->ttl('mykey') . PHP_EOL; // >>> 10
// Overwriting a key with SET clears its expiry.
echo $r->set('mykey', 'Hello World') . PHP_EOL; // >>> OK
echo $r->ttl('mykey') . PHP_EOL; // >>> -1
// XX only sets the expiry when one already exists, so this is a no-op.
echo $r->expire('mykey', 10, 'XX') . PHP_EOL; // >>> 0
echo $r->ttl('mykey') . PHP_EOL; // >>> -1
// NX only sets the expiry when there is none, so this one applies.
echo $r->expire('mykey', 10, 'NX') . PHP_EOL; // >>> 1
$expireTtl = $r->ttl('mykey');
echo $expireTtl . PHP_EOL; // >>> 10
echo $r->set('mykey', 'Hello') . PHP_EOL; // >>> OK
echo $r->expire('mykey', 10) . PHP_EOL; // >>> 1
$ttlResult = $r->ttl('mykey');
echo $ttlResult . PHP_EOL; // >>> 10
}
}
match r.sadd("myset", &["1", "2", "3", "foo", "foobar", "feelsgood"]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 6
},
Err(e) => {
println!("Error adding to set: {e}");
return;
}
}
match r.sscan_match("myset", "f*") {
Ok(iter) => {
let res: Vec<String> = iter.filter_map(|r| r.ok()).collect();
println!("{res:?}"); // >>> ["foo", "foobar", "feelsgood"]
},
Err(e) => {
println!("Error scanning set: {e}");
return;
}
}
mod cmds_generic_tests {
use redis::{Commands};
fn run() {
let mut r = match redis::Client::open("redis://127.0.0.1") {
Ok(client) => {
match client.get_connection() {
Ok(conn) => conn,
Err(e) => {
println!("Failed to connect to Redis: {e}");
return;
}
}
},
Err(e) => {
println!("Failed to create Redis client: {e}");
return;
}
};
if let Ok(res) = r.set("key1", "Hello") {
let res: String = res;
println!("{res}"); // >>> OK
}
if let Ok(res) = r.set("key2", "World") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.del(&["key1", "key2", "key3"]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 2
},
Err(e) => {
println!("Error deleting keys: {e}");
return;
}
}
if let Ok(res) = r.set("key1", "Hello") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.exists("key1") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
match r.exists("nosuchkey") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 0
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
if let Ok(res) = r.set("key2", "World") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.exists(&["key1", "key2", "nosuchkey"]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 2
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.expire("mykey", 10) {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello World") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> -1
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
// Note: Rust redis client doesn't support expire with NX/XX flags directly
// This simulates the Python behavior but without the exact flags
// Try to expire a key that doesn't have expiration (simulates xx=True failing)
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("false"); // >>> false (simulating expire xx=True failure)
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> -1
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
// Now set expiration (simulates nx=True succeeding)
match r.expire("mykey", 10) {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello") {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.expire("mykey", 10) {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey") {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
match r.mset(&[("firstname", "Jack"), ("lastname", "Stuntman"), ("age", "35")]) {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> OK
},
Err(e) => {
println!("Error setting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("*name*") {
Ok(res) => {
let mut sorted_res = res.clone();
sorted_res.sort();
println!("{sorted_res:?}"); // >>> ["firstname", "lastname"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("a??") {
Ok(res) => {
println!("{res:?}"); // >>> ["age"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("*") {
Ok(res) => {
let mut sorted_res = res.clone();
sorted_res.sort();
println!("{sorted_res:?}"); // >>> ["age", "firstname", "lastname"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.sadd("myset", &["1", "2", "3", "foo", "foobar", "feelsgood"]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 6
},
Err(e) => {
println!("Error adding to set: {e}");
return;
}
}
match r.sscan_match("myset", "f*") {
Ok(iter) => {
let res: Vec<String> = iter.filter_map(|r| r.ok()).collect();
println!("{res:?}"); // >>> ["foo", "foobar", "feelsgood"]
},
Err(e) => {
println!("Error scanning set: {e}");
return;
}
}
// Note: Rust redis client scan_match returns an iterator, not cursor-based
// This simulates the Python cursor-based output but uses the available API
match r.scan_match("*11*") {
Ok(iter) => {
let keys: Vec<String> = iter.filter_map(|r| r.ok()).collect();
},
Err(e) => {
println!("Error scanning keys: {e}");
return;
}
}
match r.geo_add("geokey", &[(0.0, 0.0, "value")]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding geo location: {e}");
return;
}
}
match r.zadd("zkey", "value", 1000) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding to sorted set: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("geokey") {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("zkey") {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
// Note: Rust redis client doesn't support scan by type directly
// We'll manually check the types of our known keys
let mut zset_keys = Vec::new();
for key in &["geokey", "zkey"] {
match r.key_type::<&str, redis::ValueType>(key) {
Ok(key_type) => {
if format!("{key_type:?}") == "ZSet" {
zset_keys.push(key.to_string());
}
},
Err(_) => {},
}
}
println!("{:?}", zset_keys); // >>> ["zkey", "geokey"]
match r.hset("myhash", "a", "1") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash field: {e}");
return;
}
}
match r.hset("myhash", "b", "2") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash fields: {e}");
return;
}
}
match r.hscan("myhash") {
Ok(iter) => {
let fields: std::collections::HashMap<String, String> = iter.filter_map(|r| r.ok()).collect();
println!("{fields:?}"); // >>> {"a": "1", "b": "2"}
},
Err(e) => {
println!("Error scanning hash: {e}");
return;
}
}
// Scan hash keys only (no values)
match r.hkeys("myhash") {
Ok(keys) => {
let keys: Vec<String> = keys;
println!("{keys:?}"); // >>> ["a", "b"]
},
Err(e) => {
println!("Error getting hash keys: {e}");
return;
}
}
}
}
match r.sadd("myset", &["1", "2", "3", "foo", "foobar", "feelsgood"]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 6
},
Err(e) => {
println!("Error adding to set: {e}");
return;
}
}
let res = match r.sscan_match("myset", "f*").await {
Ok(iter) => {
let res: Vec<Result<String, _>> = iter.collect().await;
res.into_iter().filter_map(|r| r.ok()).collect::<Vec<String>>()
},
Err(e) => {
println!("Error scanning set: {e}");
return;
}
};
println!("{res:?}"); // >>> ["foo", "foobar", "feelsgood"]
mod cmds_generic_tests {
use redis::AsyncCommands;
use futures_util::StreamExt;
async fn run() {
let mut r = match redis::Client::open("redis://127.0.0.1") {
Ok(client) => {
match client.get_multiplexed_async_connection().await {
Ok(conn) => conn,
Err(e) => {
println!("Failed to connect to Redis: {e}");
return;
}
}
},
Err(e) => {
println!("Failed to create Redis client: {e}");
return;
}
};
if let Ok(res) = r.set("key1", "Hello").await {
let res: String = res;
println!("{res}"); // >>> OK
}
if let Ok(res) = r.set("key2", "World").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.del(&["key1", "key2", "key3"]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 2
},
Err(e) => {
println!("Error deleting keys: {e}");
return;
}
}
if let Ok(res) = r.set("key1", "Hello").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.exists("key1").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
match r.exists("nosuchkey").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 0
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
if let Ok(res) = r.set("key2", "World").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.exists(&["key1", "key2", "nosuchkey"]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 2
},
Err(e) => {
println!("Error checking key existence: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.expire("mykey", 10).await {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello World").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> -1
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
// Note: Rust redis client doesn't support expire with NX/XX flags directly
// This simulates the Python behavior but without the exact flags
// Try to expire a key that doesn't have expiration (simulates xx=True failing)
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("false"); // >>> false (simulating expire xx=True failure)
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> -1
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
// Now set expiration (simulates nx=True succeeding)
match r.expire("mykey", 10).await {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
if let Ok(res) = r.set("mykey", "Hello").await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.expire("mykey", 10).await {
Ok(res) => {
let res: bool = res;
println!("{res}"); // >>> true
},
Err(e) => {
println!("Error setting key expiration: {e}");
return;
}
}
match r.ttl("mykey").await {
Ok(res) => {
let res: i64 = res;
println!("{res}"); // >>> 10
},
Err(e) => {
println!("Error getting key TTL: {e}");
return;
}
}
match r.mset(&[("firstname", "Jack"), ("lastname", "Stuntman"), ("age", "35")]).await {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> OK
},
Err(e) => {
println!("Error setting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("*name*").await {
Ok(res) => {
let mut sorted_res = res.clone();
sorted_res.sort();
println!("{sorted_res:?}"); // >>> ["firstname", "lastname"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("a??").await {
Ok(res) => {
println!("{res:?}"); // >>> ["age"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.keys::<&str, Vec<String>>("*").await {
Ok(res) => {
let mut sorted_res = res.clone();
sorted_res.sort();
println!("{sorted_res:?}"); // >>> ["age", "firstname", "lastname"]
},
Err(e) => {
println!("Error getting keys: {e}");
return;
}
}
match r.sadd("myset", &["1", "2", "3", "foo", "foobar", "feelsgood"]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 6
},
Err(e) => {
println!("Error adding to set: {e}");
return;
}
}
let res = match r.sscan_match("myset", "f*").await {
Ok(iter) => {
let res: Vec<Result<String, _>> = iter.collect().await;
res.into_iter().filter_map(|r| r.ok()).collect::<Vec<String>>()
},
Err(e) => {
println!("Error scanning set: {e}");
return;
}
};
println!("{res:?}"); // >>> ["foo", "foobar", "feelsgood"]
// Note: Rust redis client scan_match returns an iterator, not cursor-based
// This simulates the Python cursor-based output but uses the available API
let keys = match r.scan_match("*11*").await {
Ok(iter) => {
let keys: Vec<Result<String, _>> = iter.collect().await;
keys.into_iter().filter_map(|r| r.ok()).collect::<Vec<String>>()
},
Err(e) => {
println!("Error scanning keys: {e}");
return;
}
};
match r.geo_add("geokey", &[(0.0, 0.0, "value")]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding geo location: {e}");
return;
}
}
match r.zadd("zkey", "value", 1000).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding to sorted set: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("geokey").await {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("zkey").await {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
// Note: Rust redis client doesn't support scan by type directly
// We'll manually check the types of our known keys
let mut zset_keys = Vec::new();
for key in &["geokey", "zkey"] {
match r.key_type::<&str, redis::ValueType>(key).await {
Ok(key_type) => {
if format!("{key_type:?}") == "ZSet" {
zset_keys.push(key.to_string());
}
},
Err(_) => {},
}
}
println!("{:?}", zset_keys); // >>> ["zkey", "geokey"]
match r.hset("myhash", "a", "1").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash field: {e}");
return;
}
}
match r.hset("myhash", "b", "2").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash fields: {e}");
return;
}
}
let fields = match r.hscan("myhash").await {
Ok(iter) => {
let items: Vec<Result<(String, String), _>> = iter.collect().await;
items.into_iter().filter_map(|r| r.ok()).collect::<std::collections::HashMap<String, String>>()
},
Err(e) => {
println!("Error scanning hash: {e}");
return;
}
};
println!("{fields:?}"); // >>> {"a": "1", "b": "2"}
// Scan hash keys only (no values)
match r.hkeys("myhash").await {
Ok(keys) => {
let keys: Vec<String> = keys;
println!("{keys:?}"); // >>> ["a", "b"]
},
Err(e) => {
println!("Error getting hash keys: {e}");
return;
}
}
}
}
It is important to note that the MATCH filter is applied after elements are retrieved from the collection, just before returning data to the client. This means that if the pattern matches very little elements inside the collection, SCAN will likely return no elements in most iterations. An example is shown below:
> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
> scan 176 MATCH *11* COUNT 1000
1) "0"
2) 1) "key:611"
2) "key:711"
3) "key:118"
4) "key:117"
5) "key:311"
6) "key:112"
7) "key:111"
8) "key:110"
9) "key:113"
10) "key:211"
11) "key:411"
12) "key:115"
13) "key:116"
14) "key:114"
15) "key:119"
16) "key:811"
17) "key:511"
18) "key:11"
total = 0
cursor, keys = r.scan(cursor=0, 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*')
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
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
// 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);
scan2Total += scan2Keys.length;
console.log(scan2Keys.length);
// The per-call split isn't guaranteed, but the cumulative total is.
console.log(scan2Total); // >>> 19
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
String scan2Cursor = "0";
ScanResult<String> 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));
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
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all. Each iteration is awaited because
// the next one needs the cursor this one returns.
KeyScanCursor<String> 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());
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Cursor = asyncCommands
.scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000))
.toCompletableFuture().join();
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
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all. Each iteration is subscribed to in
// turn because the next one needs the cursor this one returns.
KeyScanCursor<String> 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());
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Cursor = reactiveCommands
.scan(scan2Cursor, ScanArgs.Builder.matches("*11*").limit(1000)).block();
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
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
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()
if err != nil {
panic(err)
}
scan2Total += len(scan2Keys)
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
scan2Keys, _, err = rdb.Scan(ctx, scan2Cursor, "*11*", 1000).Result()
if err != nil {
panic(err)
}
scan2Total += len(scan2Keys)
// The per-call split isn't guaranteed, but the cumulative total is.
fmt.Println(scan2Total) // >>> 19
// MATCH is applied after elements are fetched, so with the default COUNT most
// iterations return few keys or none at all.
char cursor[64] = "0";
for (int i = 0; i < 4; i++) {
reply = redisCommand(c, "SCAN %s MATCH *11*", cursor);
snprintf(cursor, sizeof(cursor), "%s", reply->element[0]->str);
printf("%zu\n", reply->element[1]->elements);
freeReplyObject(reply);
}
// A larger COUNT forces more scanning in a single iteration, so the remaining
// matches arrive together. This continues from the cursor reached above.
reply = redisCommand(c, "SCAN %s MATCH *11* COUNT 1000", cursor);
printf("%zu\n", reply->element[1]->elements);
// >>> 18
freeReplyObject(reply);
// 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]);
$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
// Note: Rust redis client scan_match returns an iterator, not cursor-based
// This simulates the Python cursor-based output but uses the available API
match r.scan_match("*11*") {
Ok(iter) => {
let keys: Vec<String> = iter.filter_map(|r| r.ok()).collect();
},
Err(e) => {
println!("Error scanning keys: {e}");
return;
}
}
// Note: Rust redis client scan_match returns an iterator, not cursor-based
// This simulates the Python cursor-based output but uses the available API
let keys = match r.scan_match("*11*").await {
Ok(iter) => {
let keys: Vec<Result<String, _>> = iter.collect().await;
keys.into_iter().filter_map(|r| r.ok()).collect::<Vec<String>>()
},
Err(e) => {
println!("Error scanning keys: {e}");
return;
}
};
As you can see most of the calls returned zero elements, but the last call where a COUNT of 1000 was used in order to force the command to do more scanning for that iteration.
When using Redis Cluster, the search is optimized for patterns that imply a single slot.
If a pattern can only match keys of one slot,
Redis only iterates over keys in that slot, rather than the whole database,
when searching for keys matching the pattern.
For example, with the pattern {a}h*llo, Redis would only try to match it with the keys in slot 15495, which hash tag {a} implies.
To use pattern with hash tag, see Hash tags in the Cluster specification for more information.
The TYPE option
You can use the TYPE option to ask SCAN to only return objects that match a given type, allowing you to iterate through the database looking for keys of a specific type. The TYPE option is only available on the whole-database SCAN, not HSCAN or ZSCAN etc.
The type argument is the same string name that the TYPE command returns. Note a quirk where some Redis types, such as GeoHashes, HyperLogLogs, Bitmaps, and Bitfields, may internally be implemented using other Redis types, such as a string or zset, so can't be distinguished from other keys of that same type by SCAN. For example, a ZSET and GEOHASH:
> GEOADD geokey 0 0 value (integer) 1 > ZADD zkey 1000 value (integer) 1 > TYPE geokey zset > TYPE zkey zset > SCAN 0 TYPE zset 1) "0" 2) 1) "geokey" 2) "zkey"
res = r.geoadd("geokey", (0, 0, "value"))
print(res)
# >>> 1
res = r.zadd("zkey", {"value": 1000})
print(res)
# >>> 1
res = r.type("geokey")
print(res)
# >>> zset
res = r.type("zkey")
print(res)
# >>> zset
# 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']
const scan3Res1 = await client.geoAdd('geokey', { longitude: 0, latitude: 0, member: 'value' });
console.log(scan3Res1); // 1
const scan3Res2 = await client.zAdd('zkey', [{ score: 1000, value: 'value' }]);
console.log(scan3Res2); // 1
const scan3Res3 = await client.type('geokey');
console.log(scan3Res3); // zset
const scan3Res4 = await client.type('zkey');
console.log(scan3Res4); // 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 {
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']
const scan3Res1 = await redis.geoadd('geokey', '0', '0', 'value');
console.log(scan3Res1); // >>> 1
const scan3Res2 = await redis.zadd('zkey', '1000', 'value');
console.log(scan3Res2); // >>> 1
console.log(await redis.type('geokey')); // >>> zset
console.log(await redis.type('zkey')); // >>> 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']
long scan3Result1 = jedis.geoadd("geokey", 0, 0, "value");
System.out.println(scan3Result1); // >>> 1
long scan3Result2 = jedis.zadd("zkey", 1000, "value");
System.out.println(scan3Result2); // >>> 1
System.out.println(jedis.type("geokey")); // >>> zset
System.out.println(jedis.type("zkey")); // >>> zset
// 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<String> scan3Keys = new ArrayList<>();
do {
ScanResult<String> 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]
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<String> scan3Keys = new java.util.ArrayList<>();
KeyScanCursor<String> 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]
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<String> scan3Keys = new java.util.ArrayList<>();
KeyScanCursor<String> 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]
scan3Result1, err := rdb.GeoAdd(ctx, "geokey", &redis.GeoLocation{
Longitude: 0, Latitude: 0, Name: "value",
}).Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result1) // >>> 1
scan3Result2, err := rdb.ZAdd(ctx, "zkey", redis.Z{Score: 1000, Member: "value"}).Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result2) // >>> 1
scan3Result3, err := rdb.Type(ctx, "geokey").Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result3) // >>> zset
scan3Result4, err := rdb.Type(ctx, "zkey").Result()
if err != nil {
panic(err)
}
fmt.Println(scan3Result4) // >>> zset
// 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
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(scan3Keys)
fmt.Println(scan3Keys) // >>> [geokey zkey]
reply = redisCommand(c, "GEOADD geokey 0 0 value");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "ZADD zkey 1000 value");
printf("%lld\n", reply->integer);
// >>> 1
freeReplyObject(reply);
reply = redisCommand(c, "TYPE geokey");
printf("%s\n", reply->str);
// >>> zset
freeReplyObject(reply);
reply = redisCommand(c, "TYPE zkey");
printf("%s\n", reply->str);
// >>> zset
freeReplyObject(reply);
reply = redisCommand(c, "SCAN 0 TYPE zset");
printf("%zu\n", reply->element[1]->elements);
// >>> 2
freeReplyObject(reply);
match r.geo_add("geokey", &[(0.0, 0.0, "value")]) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding geo location: {e}");
return;
}
}
match r.zadd("zkey", "value", 1000) {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding to sorted set: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("geokey") {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("zkey") {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
// Note: Rust redis client doesn't support scan by type directly
// We'll manually check the types of our known keys
let mut zset_keys = Vec::new();
for key in &["geokey", "zkey"] {
match r.key_type::<&str, redis::ValueType>(key) {
Ok(key_type) => {
if format!("{key_type:?}") == "ZSet" {
zset_keys.push(key.to_string());
}
},
Err(_) => {},
}
}
println!("{:?}", zset_keys); // >>> ["zkey", "geokey"]
match r.geo_add("geokey", &[(0.0, 0.0, "value")]).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding geo location: {e}");
return;
}
}
match r.zadd("zkey", "value", 1000).await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error adding to sorted set: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("geokey").await {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
match r.key_type::<&str, redis::ValueType>("zkey").await {
Ok(res) => {
println!("{res:?}"); // >>> zset
},
Err(e) => {
println!("Error getting key type: {e}");
return;
}
}
// Note: Rust redis client doesn't support scan by type directly
// We'll manually check the types of our known keys
let mut zset_keys = Vec::new();
for key in &["geokey", "zkey"] {
match r.key_type::<&str, redis::ValueType>(key).await {
Ok(key_type) => {
if format!("{key_type:?}") == "ZSet" {
zset_keys.push(key.to_string());
}
},
Err(_) => {},
}
}
println!("{:?}", zset_keys); // >>> ["zkey", "geokey"]
It is important to note that the TYPE filter is also applied after elements are retrieved from the database, so the option does not reduce the amount of work the server has to do to complete a full iteration, and for rare types you may receive no elements in many iterations.
The NOVALUES option
When using HSCAN, you can use the NOVALUES option to make Redis return only the keys in the hash table without their corresponding values.
> HSET myhash a 1 b 2 OK > HSCAN myhash 0 1) "0" 2) 1) "a" 2) "1" 3) "b" 4) "2" > HSCAN myhash 0 NOVALUES 1) "0" 2) 1) "a" 2) "b"
res = r.hset("myhash", mapping={"a": 1, "b": 2})
print(res)
# >>> 2
cursor, keys = r.hscan("myhash", 0)
print(keys)
# >>> {'a': '1', 'b': '2'}
cursor, keys = r.hscan("myhash", 0, no_values=True)
print(sorted(keys))
# >>> ['a', 'b']
const scan4Res1 = await client.hSet('myhash', { a: 1, b: 2 });
console.log(scan4Res1); // 2
// HSCAN doesn't promise a field order, so pair entries into an object rather than
// relying on position.
const scan4Res2 = await client.hScan('myhash', '0');
const scan4Pairs = Object.fromEntries(scan4Res2.entries.map((e) => [e.field, e.value]));
console.log(scan4Pairs); // {a: '1', b: '2'}
const scan4Res3 = await client.hScan('myhash', '0', { COUNT: 10 });
const items = scan4Res3.entries.map((item) => item.field).sort()
console.log(items); // ['a', 'b']
const scan4Res1 = await redis.hset('myhash', { a: 1, b: 2 });
console.log(scan4Res1); // >>> 2
// HSCAN returns field and value interleaved. Redis does not promise an order, so pair
// them up into an object rather than relying on the position of each element.
const [, scan4Flat] = await redis.hscan('myhash', 0);
const scan4Pairs = Object.fromEntries(
scan4Flat.reduce((acc, v, i) => (i % 2 ? acc : [...acc, [v, scan4Flat[i + 1]]]), [])
);
console.log(scan4Pairs); // >>> { a: '1', b: '2' }
const [, scan4Fields] = await redis.hscan('myhash', 0, 'NOVALUES');
console.log(scan4Fields.sort()); // >>> [ 'a', 'b' ]
long scan4Result1 = jedis.hset("myhash", Map.of("a", "1", "b", "2"));
System.out.println(scan4Result1); // >>> 2
ScanResult<Map.Entry<String, String>> scan4Result2 = jedis.hscan(
"myhash", "0", new ScanParams()
);
ArrayList<String> scan4Pairs = new ArrayList<>();
for (Map.Entry<String, String> entry : scan4Result2.getResult()) {
scan4Pairs.add(entry.getKey() + "=" + entry.getValue());
}
Collections.sort(scan4Pairs);
System.out.println(scan4Pairs); // >>> [a=1, b=2]
ScanResult<String> scan4Result3 = jedis.hscanNoValues(
"myhash", "0", new ScanParams()
);
ArrayList<String> scan4Fields = new ArrayList<>(scan4Result3.getResult());
Collections.sort(scan4Fields);
System.out.println(scan4Fields); // >>> [a, b]
CompletableFuture<Void> scan4Example = asyncCommands
.hset("myhash", Map.of("a", "1", "b", "2"))
.thenCompose(scan4Res1 -> {
System.out.println(scan4Res1); // >>> 2
return asyncCommands.hscan("myhash");
})
.thenCompose(scan4Res2 -> {
System.out.println(new java.util.TreeMap<>(scan4Res2.getMap()));
// >>> {a=1, b=2}
return asyncCommands.hscanNovalues("myhash");
})
.thenAccept(scan4Res3 -> {
List<String> fields = new java.util.ArrayList<>(scan4Res3.getKeys());
Collections.sort(fields);
System.out.println(fields); // >>> [a, b]
})
.toCompletableFuture();
Mono<Void> scan4Example = reactiveCommands
.hset("myhash", Map.of("a", "1", "b", "2"))
.flatMap(scan4Res1 -> {
System.out.println(scan4Res1); // >>> 2
return reactiveCommands.hscan("myhash");
})
.flatMap(scan4Res2 -> {
System.out.println(new java.util.TreeMap<>(scan4Res2.getMap()));
// >>> {a=1, b=2}
return reactiveCommands.hscanNovalues("myhash");
})
.doOnNext(scan4Res3 -> {
List<String> fields = new java.util.ArrayList<>(scan4Res3.getKeys());
Collections.sort(fields);
System.out.println(fields); // >>> [a, b]
})
.then();
scan4Result1, err := rdb.HSet(ctx, "myhash", "a", 1, "b", 2).Result()
if err != nil {
panic(err)
}
fmt.Println(scan4Result1) // >>> 2
scan4Result2, _, err := rdb.HScan(ctx, "myhash", 0, "", 0).Result()
if err != nil {
panic(err)
}
// HSCAN returns field and value interleaved. Redis does not promise an order, so
// collect the pairs into a map: fmt prints map keys sorted, whatever order they arrived in.
scan4Fields := map[string]string{}
for i := 0; i < len(scan4Result2); i += 2 {
scan4Fields[scan4Result2[i]] = scan4Result2[i+1]
}
fmt.Println(scan4Fields) // >>> map[a:1 b:2]
scan4Result3, _, err := rdb.HScanNoValues(ctx, "myhash", 0, "", 0).Result()
if err != nil {
panic(err)
}
sort.Strings(scan4Result3)
fmt.Println(scan4Result3) // >>> [a b]
reply = redisCommand(c, "HSET myhash a 1 b 2");
printf("%lld\n", reply->integer);
// >>> 2
freeReplyObject(reply);
// Without NOVALUES the results alternate field, value, field, value.
reply = redisCommand(c, "HSCAN myhash 0");
for (size_t i = 0; i < reply->element[1]->elements; i += 2) {
printf("%s=%s\n", reply->element[1]->element[i]->str,
reply->element[1]->element[i + 1]->str);
}
// >>> a=1
// >>> b=2
freeReplyObject(reply);
reply = redisCommand(c, "HSCAN myhash 0 NOVALUES");
for (size_t i = 0; i < reply->element[1]->elements; i++) {
printf("%s\n", reply->element[1]->element[i]->str);
}
// >>> a
// >>> b
freeReplyObject(reply);
$scan4Result1 = $r->hset('myhash', 'a', 1, 'b', 2);
echo $scan4Result1 . PHP_EOL; // >>> 2
[$scan4Cursor, $scan4Pairs] = $r->hscan('myhash', 0);
echo json_encode($scan4Pairs) . PHP_EOL; // >>> {"a":"1","b":"2"}
// Redis does not promise a field order, so sort before comparing.
[$scan4Cursor, $scan4Fields] = $r->hscan('myhash', 0, ['NOVALUES' => true]);
sort($scan4Fields);
echo implode(', ', $scan4Fields) . PHP_EOL; // >>> a, b
match r.hset("myhash", "a", "1") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash field: {e}");
return;
}
}
match r.hset("myhash", "b", "2") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash fields: {e}");
return;
}
}
match r.hscan("myhash") {
Ok(iter) => {
let fields: std::collections::HashMap<String, String> = iter.filter_map(|r| r.ok()).collect();
println!("{fields:?}"); // >>> {"a": "1", "b": "2"}
},
Err(e) => {
println!("Error scanning hash: {e}");
return;
}
}
// Scan hash keys only (no values)
match r.hkeys("myhash") {
Ok(keys) => {
let keys: Vec<String> = keys;
println!("{keys:?}"); // >>> ["a", "b"]
},
Err(e) => {
println!("Error getting hash keys: {e}");
return;
}
}
match r.hset("myhash", "a", "1").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash field: {e}");
return;
}
}
match r.hset("myhash", "b", "2").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 1
},
Err(e) => {
println!("Error setting hash fields: {e}");
return;
}
}
let fields = match r.hscan("myhash").await {
Ok(iter) => {
let items: Vec<Result<(String, String), _>> = iter.collect().await;
items.into_iter().filter_map(|r| r.ok()).collect::<std::collections::HashMap<String, String>>()
},
Err(e) => {
println!("Error scanning hash: {e}");
return;
}
};
println!("{fields:?}"); // >>> {"a": "1", "b": "2"}
// Scan hash keys only (no values)
match r.hkeys("myhash").await {
Ok(keys) => {
let keys: Vec<String> = keys;
println!("{keys:?}"); // >>> ["a", "b"]
},
Err(e) => {
println!("Error getting hash keys: {e}");
return;
}
}
Multiple parallel iterations
It is possible for an infinite number of clients to iterate the same collection at the same time, as the full state of the iterator is in the cursor, that is obtained and returned to the client at every call. No server side state is taken at all.
Terminating iterations in the middle
Since there is no state server side, but the full state is captured by the cursor, the caller is free to terminate an iteration half-way without signaling this to the server in any way. An infinite number of iterations can be started and never terminated without any issue.
Calling SCAN with a corrupted cursor
Calling SCAN with a broken, negative, out of range, or otherwise invalid cursor, will result in undefined behavior but never in a crash. What will be undefined is that the guarantees about the returned elements can no longer be ensured by the SCAN implementation.
The only valid cursors to use are:
- The cursor value of 0 when starting an iteration.
- The cursor returned by the previous call to SCAN in order to continue the iteration.
Guarantee of termination
The SCAN algorithm is guaranteed to terminate only if the size of the iterated collection remains bounded to a given maximum size, otherwise iterating a collection that always grows may result into SCAN to never terminate a full iteration.
This is easy to see intuitively: if the collection grows there is more and more work to do in order to visit all the possible elements, and the ability to terminate the iteration depends on the number of calls to SCAN and its COUNT option value compared with the rate at which the collection grows.
Why SCAN may return all the items of an aggregate data type in a single call?
In the COUNT option documentation, we state that sometimes this family of commands may return all the elements of a Set, Hash or Sorted Set at once in a single call, regardless of the COUNT option value. The reason why this happens is that the cursor-based iterator can be implemented, and is useful, only when the aggregate data type that we are scanning is represented as a hash table. However Redis uses a memory optimization where small aggregate data types, until they reach a given amount of items or a given max size of single elements, are represented using a compact single-allocation packed encoding. When this is the case, SCAN has no meaningful cursor to return, and must iterate the whole data structure at once, so the only sane behavior it has is to return everything in a call.
However once the data structures are bigger and are promoted to use real hash tables, the SCAN family of commands will resort to the normal behavior. Note that since this special behavior of returning all the elements is true only for small aggregates, it has no effects on the command complexity or latency. However the exact limits to get converted into real hash tables are user configurable, so the maximum number of elements you can see returned in a single call depends on how big an aggregate data type could be and still use the packed representation.
Also note that this behavior is specific of SSCAN, HSCAN and ZSCAN. SCAN itself never shows this behavior because the key space is always represented by hash tables.
Further reading
For more information about managing keys, please refer to the The Redis Keyspace tutorial.
Additional examples
Give the following commands, showing iteration of a hash key, a try in the interactive console:
redis> HMSET hash name Jack age 33 OK redis> HSCAN hash 0 1) "0" 2) 1) "name" 2) "Jack" 3) "age" 4) "33"
Redis Software and Redis Cloud compatibility
| Redis Software |
Redis Cloud |
Notes |
|---|---|---|
| ✅ Standard |
✅ Standard |
Return information
Array reply: specifically, an array with two elements.
- The first element is a Bulk string reply that represents an unsigned 64-bit number, the cursor.
- The second element is an Array reply with the names of scanned keys.
History
- Starting with Redis version 6.0.0: Added the
TYPEsubcommand.