Skip to content

Commit c939c8a

Browse files
christophstroblThomas Darimont
authored andcommitted
DATAREDIS-306 - Add support for ZSCAN.
ZSCAN is directly supported by jedis and can be emulated for lettuce via eval. SRP and JRedis will throw UnsupportedOperationException. Original pull request: spring-projects#80.
1 parent f6e8898 commit c939c8a

16 files changed

+346
-51
lines changed

src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,6 +2228,14 @@ public Cursor<byte[]> scan(ScanOptions options) {
22282228
return this.delegate.scan(options);
22292229
}
22302230

2231+
/*
2232+
*
2233+
*/
2234+
@Override
2235+
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
2236+
return this.delegate.zScan(key, options);
2237+
}
2238+
22312239
/*
22322240
* (non-Javadoc)
22332241
* @see org.springframework.data.redis.connection.RedisSetCommands#scan(byte[], org.springframework.data.redis.core.ScanOptions)
@@ -2320,24 +2328,6 @@ public String getClientName() {
23202328
return this.delegate.getClientName();
23212329
}
23222330

2323-
/*
2324-
* (non-Javadoc)
2325-
* @see org.springframework.data.redis.connection.StringRedisConnection#sScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
2326-
*/
2327-
@Override
2328-
public Cursor<String> sScan(String key, ScanOptions options) {
2329-
2330-
return new ConvertingCursor<byte[], String>(this.delegate.sScan(this.serialize(key), options),
2331-
new Converter<byte[], String>() {
2332-
2333-
@Override
2334-
public String convert(byte[] source) {
2335-
return serializer.deserialize(source);
2336-
}
2337-
});
2338-
2339-
}
2340-
23412331
/*
23422332
* (non-Javadoc)
23432333
* @see org.springframework.data.redis.connection.StringRedisConnection#hScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
@@ -2354,12 +2344,12 @@ public Entry<String, String> convert(final Entry<byte[], byte[]> source) {
23542344

23552345
@Override
23562346
public String getKey() {
2357-
return DefaultStringRedisConnection.this.serializer.deserialize(source.getKey());
2347+
return bytesToString.convert(source.getKey());
23582348
}
23592349

23602350
@Override
23612351
public String getValue() {
2362-
return DefaultStringRedisConnection.this.serializer.deserialize(source.getValue());
2352+
return bytesToString.convert(source.getValue());
23632353
}
23642354

23652355
@Override
@@ -2370,4 +2360,24 @@ public String setValue(String value) {
23702360
}
23712361
});
23722362
}
2363+
2364+
/*
2365+
* (non-Javadoc)
2366+
* @see org.springframework.data.redis.connection.StringRedisConnection#sScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
2367+
*/
2368+
@Override
2369+
public Cursor<String> sScan(String key, ScanOptions options) {
2370+
return new ConvertingCursor<byte[], String>(this.delegate.sScan(this.serialize(key), options), bytesToString);
2371+
}
2372+
2373+
/*
2374+
* (non-Javadoc)
2375+
* @see org.springframework.data.redis.connection.StringRedisConnection#zScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
2376+
*/
2377+
@Override
2378+
public Cursor<StringTuple> zScan(String key, ScanOptions options) {
2379+
return new ConvertingCursor<Tuple, StringRedisConnection.StringTuple>(delegate.zScan(this.serialize(key), options),
2380+
new TupleConverter());
2381+
}
2382+
23732383
}

src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import java.util.Set;
1919

20+
import org.springframework.data.redis.core.Cursor;
21+
import org.springframework.data.redis.core.ScanOptions;
22+
2023
/**
2124
* ZSet(SortedSet)-specific commands supported by Redis.
2225
*
@@ -332,4 +335,14 @@ public interface Tuple extends Comparable<Double> {
332335
* @return
333336
*/
334337
Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets);
338+
339+
/**
340+
* Use a {@link Cursor} to iterate over elements in sorted set at {@code key}.
341+
*
342+
* @param key
343+
* @param options
344+
* @return
345+
* @since 1.4
346+
*/
347+
Cursor<Tuple> zScan(byte[] key, ScanOptions options);
335348
}

src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,15 @@ public interface StringTuple extends Tuple {
319319
*/
320320
List<RedisClientInfo> getClientList();
321321

322+
/**
323+
* @see RedisHashCommands#hScan(byte[], ScanOptions)
324+
* @param key
325+
* @param options
326+
* @return
327+
* @since 1.4
328+
*/
329+
Cursor<Map.Entry<String, String>> hScan(String key, ScanOptions options);
330+
322331
/**
323332
* @see RedisSetCommands#sScan(byte[], ScanOptions)
324333
* @param key
@@ -329,10 +338,11 @@ public interface StringTuple extends Tuple {
329338
Cursor<String> sScan(String key, ScanOptions options);
330339

331340
/**
341+
* @see RedisZSetCommands#zScan(byte[], ScanOptions)
332342
* @param key
333343
* @param options
334344
* @return
335345
* @since 1.4
336346
*/
337-
Cursor<Map.Entry<String, String>> hScan(String key, ScanOptions options);
347+
Cursor<StringTuple> zScan(String key, ScanOptions options);
338348
}

src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.redis.connection.RedisConnection;
3939
import org.springframework.data.redis.connection.RedisPipelineException;
4040
import org.springframework.data.redis.connection.RedisSubscribedConnectionException;
41+
import org.springframework.data.redis.connection.RedisZSetCommands;
4142
import org.springframework.data.redis.connection.ReturnType;
4243
import org.springframework.data.redis.connection.SortParameters;
4344
import org.springframework.data.redis.connection.Subscription;
@@ -2949,6 +2950,43 @@ protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
29492950

29502951
}
29512952

2953+
/*
2954+
* (non-Javadoc)
2955+
* @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions)
2956+
*/
2957+
@Override
2958+
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
2959+
return zScan(key, 0L, options);
2960+
}
2961+
2962+
/**
2963+
* @param key
2964+
* @param cursorId
2965+
* @param options
2966+
* @return
2967+
* @since 1.4
2968+
*/
2969+
public Cursor<Tuple> zScan(byte[] key, Long cursorId, ScanOptions options) {
2970+
2971+
return new KeyBoundCursor<Tuple>(key, cursorId, options) {
2972+
2973+
@Override
2974+
protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions options) {
2975+
2976+
if (isQueueing() || isPipelined()) {
2977+
throw new UnsupportedOperationException("'ZSCAN' cannot be called in pipeline / transaction mode.");
2978+
}
2979+
2980+
ScanParams params = prepareScanParams(options);
2981+
2982+
ScanResult<redis.clients.jedis.Tuple> result = jedis.zscan(key, JedisConverters.toBytes(cursorId), params);
2983+
return new ScanIteration<RedisZSetCommands.Tuple>(Long.valueOf(result.getStringCursor()), JedisConverters
2984+
.tuplesToTuples().convert(result.getResult()));
2985+
}
2986+
2987+
}.open();
2988+
}
2989+
29522990
/*
29532991
* (non-Javadoc)
29542992
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)

src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ abstract public class JedisConverters extends Converters {
5959
private static final SetConverter<redis.clients.jedis.Tuple, Tuple> TUPLE_SET_TO_TUPLE_SET;
6060
private static final Converter<Exception, DataAccessException> EXCEPTION_CONVERTER = new JedisExceptionConverter();
6161
private static final Converter<String[], List<RedisClientInfo>> STRING_TO_CLIENT_INFO_CONVERTER = new StringToRedisClientInfoConverter();
62+
private static final Converter<redis.clients.jedis.Tuple, Tuple> TUPLE_CONVERTER;
63+
private static final ListConverter<redis.clients.jedis.Tuple, Tuple> TUPLE_LIST_TO_TUPLE_LIST_CONVERTER;
6264

6365
static {
6466
STRING_TO_BYTES = new Converter<String, byte[]>() {
@@ -69,19 +71,30 @@ public byte[] convert(String source) {
6971
STRING_LIST_TO_BYTE_LIST = new ListConverter<String, byte[]>(STRING_TO_BYTES);
7072
STRING_SET_TO_BYTE_SET = new SetConverter<String, byte[]>(STRING_TO_BYTES);
7173
STRING_MAP_TO_BYTE_MAP = new MapConverter<String, byte[]>(STRING_TO_BYTES);
72-
TUPLE_SET_TO_TUPLE_SET = new SetConverter<redis.clients.jedis.Tuple, Tuple>(
73-
new Converter<redis.clients.jedis.Tuple, Tuple>() {
74-
public Tuple convert(redis.clients.jedis.Tuple source) {
75-
return source != null ? new DefaultTuple(source.getBinaryElement(), source.getScore()) : null;
76-
}
74+
TUPLE_CONVERTER = new Converter<redis.clients.jedis.Tuple, Tuple>() {
75+
public Tuple convert(redis.clients.jedis.Tuple source) {
76+
return source != null ? new DefaultTuple(source.getBinaryElement(), source.getScore()) : null;
77+
}
7778

78-
});
79+
};
80+
TUPLE_SET_TO_TUPLE_SET = new SetConverter<redis.clients.jedis.Tuple, Tuple>(TUPLE_CONVERTER);
81+
TUPLE_LIST_TO_TUPLE_LIST_CONVERTER = new ListConverter<redis.clients.jedis.Tuple, Tuple>(TUPLE_CONVERTER);
7982
}
8083

8184
public static Converter<String, byte[]> stringToBytes() {
8285
return STRING_TO_BYTES;
8386
}
8487

88+
/**
89+
* {@link ListConverter} converting jedis {@link redis.clients.jedis.Tuple} to {@link Tuple}.
90+
*
91+
* @return
92+
* @since 1.4
93+
*/
94+
public static ListConverter<redis.clients.jedis.Tuple, Tuple> tuplesToTuples() {
95+
return TUPLE_LIST_TO_TUPLE_LIST_CONVERTER;
96+
}
97+
8598
public static ListConverter<String, byte[]> stringListToByteList() {
8699
return STRING_LIST_TO_BYTE_LIST;
87100
}

src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,15 @@ public Cursor<byte[]> scan(ScanOptions options) {
12491249
throw new UnsupportedOperationException("'SCAN' command is not supported for jredis.");
12501250
}
12511251

1252+
/*
1253+
* (non-Javadoc)
1254+
* @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions)
1255+
*/
1256+
@Override
1257+
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
1258+
throw new UnsupportedOperationException("'ZSCAN' command is not supported for jredis.");
1259+
}
1260+
12521261
/*
12531262
* (non-Javadoc)
12541263
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,12 +3069,52 @@ protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
30693069
List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
30703070
String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));
30713071

3072-
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), ((ArrayList<byte[]>) result.get(1)));
3072+
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), ((List<byte[]>) result.get(1)));
30733073
}
30743074
}.open();
30753075

30763076
}
30773077

3078+
/* (non-Javadoc)
3079+
* @see org.springframework.data.redis.connection.RedisHashCommands#hScan(byte[], org.springframework.data.redis.core.ScanOptions)
3080+
*/
3081+
@Override
3082+
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
3083+
return hscan(key, 0, options);
3084+
}
3085+
3086+
/**
3087+
* @param key
3088+
* @param cursorId
3089+
* @param options
3090+
* @return
3091+
* @since 1.4
3092+
*/
3093+
public Cursor<Entry<byte[], byte[]>> hscan(byte[] key, long cursorId, ScanOptions options) {
3094+
3095+
return new KeyBoundCursor<Entry<byte[], byte[]>>(key, cursorId, options) {
3096+
3097+
@Override
3098+
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId, ScanOptions options) {
3099+
3100+
if (isQueueing() || isPipelined()) {
3101+
throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode.");
3102+
}
3103+
3104+
String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId
3105+
+ options.toOptionString();
3106+
String script = "return redis.call('HSCAN'" + params + ")";
3107+
3108+
List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
3109+
String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));
3110+
3111+
@SuppressWarnings("unchecked")
3112+
Map<byte[], byte[]> values = LettuceConverters.toMap((List<byte[]>) result.get(1));
3113+
return new ScanIteration<Entry<byte[], byte[]>>(Long.valueOf(nextCursorId), values.entrySet());
3114+
}
3115+
}.open();
3116+
}
3117+
30783118
/*
30793119
* (non-Javadoc)
30803120
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
@@ -3115,12 +3155,13 @@ protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions op
31153155
}.open();
31163156
}
31173157

3118-
/* (non-Javadoc)
3119-
* @see org.springframework.data.redis.connection.RedisHashCommands#hScan(byte[], org.springframework.data.redis.core.ScanOptions)
3158+
/*
3159+
* (non-Javadoc)
3160+
* @see org.springframework.data.redis.connection.RedisZSetCommands#zScan(byte[], org.springframework.data.redis.core.ScanOptions)
31203161
*/
31213162
@Override
3122-
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
3123-
return hscan(key, 0, options);
3163+
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
3164+
return zScan(key, 0L, options);
31243165
}
31253166

31263167
/**
@@ -3130,27 +3171,27 @@ public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
31303171
* @return
31313172
* @since 1.4
31323173
*/
3133-
public Cursor<Entry<byte[], byte[]>> hscan(byte[] key, long cursorId, ScanOptions options) {
3174+
public Cursor<Tuple> zScan(byte[] key, long cursorId, ScanOptions options) {
31343175

3135-
return new KeyBoundCursor<Entry<byte[], byte[]>>(key, cursorId, options) {
3176+
return new KeyBoundCursor<Tuple>(key, cursorId, options) {
31363177

3178+
@SuppressWarnings("unchecked")
31373179
@Override
3138-
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId, ScanOptions options) {
3180+
protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions options) {
31393181

31403182
if (isQueueing() || isPipelined()) {
3141-
throw new UnsupportedOperationException("'HSCAN' cannot be called in pipeline / transaction mode.");
3183+
throw new UnsupportedOperationException("'ZSCAN' cannot be called in pipeline / transaction mode.");
31423184
}
31433185

31443186
String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId
31453187
+ options.toOptionString();
3146-
String script = "return redis.call('HSCAN'" + params + ")";
3188+
String script = "return redis.call('ZSCAN'" + params + ")";
31473189

31483190
List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
31493191
String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));
31503192

3151-
@SuppressWarnings("unchecked")
3152-
Map<byte[], byte[]> values = LettuceConverters.toMap((List<byte[]>) result.get(1));
3153-
return new ScanIteration<Entry<byte[], byte[]>>(Long.valueOf(nextCursorId), values.entrySet());
3193+
return new ScanIteration<Tuple>(Long.valueOf(nextCursorId), LettuceConverters.toTuple((List<byte[]>) result
3194+
.get(1)));
31543195
}
31553196
}.open();
31563197
}

0 commit comments

Comments
 (0)