Skip to content

Commit 01953d1

Browse files
committed
Encapsulate Redis Scan CursorId.
We now retain the raw cursor value without attempting to convert it into a long as Redis uses 64 bit unsigned integers exceeding Long.MAX_VALUE.
1 parent 8b94615 commit 01953d1

20 files changed

+328
-83
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,13 @@ public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
275275
return new ScanCursor<Entry<byte[], byte[]>>(options) {
276276

277277
@Override
278-
protected ScanIteration<Entry<byte[], byte[]>> doScan(long cursorId, ScanOptions options) {
278+
protected ScanIteration<Entry<byte[], byte[]>> doScan(CursorId cursorId, ScanOptions options) {
279279

280280
ScanParams params = JedisConverters.toScanParams(options);
281281

282282
ScanResult<Entry<byte[], byte[]>> result = connection.getCluster().hscan(key, JedisConverters.toBytes(cursorId),
283283
params);
284-
return new ScanIteration<>(Long.valueOf(result.getCursor()), result.getResult());
284+
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
285285
}
286286
}.open();
287287
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,11 @@ Cursor<byte[]> scan(RedisClusterNode node, ScanOptions options) {
177177
return new ScanCursor<byte[]>(0, options) {
178178

179179
@Override
180-
protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
180+
protected ScanIteration<byte[]> doScan(CursorId cursorId, ScanOptions options) {
181181

182182
ScanParams params = JedisConverters.toScanParams(options);
183-
ScanResult<String> result = client.scan(Long.toString(cursorId), params);
184-
return new ScanIteration<>(Long.valueOf(result.getCursor()),
183+
ScanResult<String> result = client.scan(cursorId.getCursorId(), params);
184+
return new ScanIteration<>(CursorId.of(result.getCursor()),
185185
JedisConverters.stringListToByteList().convert(result.getResult()));
186186
}
187187
}.open();

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,11 @@ public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
394394
return new ScanCursor<byte[]>(options) {
395395

396396
@Override
397-
protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
397+
protected ScanIteration<byte[]> doScan(CursorId cursorId, ScanOptions options) {
398398

399399
ScanParams params = JedisConverters.toScanParams(options);
400400
ScanResult<byte[]> result = connection.getCluster().sscan(key, JedisConverters.toBytes(cursorId), params);
401-
return new ScanIteration<>(Long.parseLong(result.getCursor()), result.getResult());
401+
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
402402
}
403403
}.open();
404404
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1079,13 +1079,13 @@ public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
10791079
return new ScanCursor<Tuple>(options) {
10801080

10811081
@Override
1082-
protected ScanIteration<Tuple> doScan(long cursorId, ScanOptions options) {
1082+
protected ScanIteration<Tuple> doScan(CursorId cursorId, ScanOptions options) {
10831083

10841084
ScanParams params = JedisConverters.toScanParams(options);
10851085

10861086
ScanResult<redis.clients.jedis.resps.Tuple> result = connection.getCluster().zscan(key,
10871087
JedisConverters.toBytes(cursorId), params);
1088-
return new ScanIteration<>(Long.valueOf(result.getCursor()),
1088+
return new ScanIteration<>(CursorId.of(result.getCursor()),
10891089
JedisConverters.tuplesToTuples().convert(result.getResult()));
10901090
}
10911091
}.open();

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

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
7676
import org.springframework.data.redis.connection.zset.DefaultTuple;
7777
import org.springframework.data.redis.connection.zset.Tuple;
78+
import org.springframework.data.redis.core.Cursor;
7879
import org.springframework.data.redis.core.ScanOptions;
7980
import org.springframework.data.redis.core.types.Expiration;
8081
import org.springframework.data.redis.core.types.RedisClientInfo;
@@ -175,6 +176,10 @@ public static byte[] toBytes(Number source) {
175176
return toBytes(String.valueOf(source));
176177
}
177178

179+
public static byte[] toBytes(Cursor.CursorId source) {
180+
return toBytes(source.getCursorId());
181+
}
182+
178183
@Nullable
179184
public static byte[] toBytes(@Nullable String source) {
180185
return source == null ? null : SafeEncoder.encode(source);

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

+8-15
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.redis.connection.RedisHashCommands;
3131
import org.springframework.data.redis.connection.convert.Converters;
3232
import org.springframework.data.redis.core.Cursor;
33+
import org.springframework.data.redis.core.Cursor.CursorId;
3334
import org.springframework.data.redis.core.KeyBoundCursor;
3435
import org.springframework.data.redis.core.ScanIteration;
3536
import org.springframework.data.redis.core.ScanOptions;
@@ -149,8 +150,7 @@ public List<Entry<byte[], byte[]>> hRandFieldWithValues(byte[] key, long count)
149150

150151
List<Entry<byte[], byte[]>> convertedMapEntryList = new ArrayList<>(mapEntryList.size());
151152

152-
mapEntryList.forEach(entry ->
153-
convertedMapEntryList.add(Converters.entryOf(entry.getKey(), entry.getValue())));
153+
mapEntryList.forEach(entry -> convertedMapEntryList.add(Converters.entryOf(entry.getKey(), entry.getValue())));
154154

155155
return convertedMapEntryList;
156156

@@ -219,34 +219,27 @@ public List<byte[]> hVals(byte[] key) {
219219

220220
@Override
221221
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
222-
return hScan(key, 0, options);
222+
return hScan(key, CursorId.initial(), options);
223223
}
224224

225-
/**
226-
* @since 1.4
227-
* @param key
228-
* @param cursorId
229-
* @param options
230-
* @return
231-
*/
232-
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, long cursorId, ScanOptions options) {
225+
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, CursorId cursorId, ScanOptions options) {
233226

234227
Assert.notNull(key, "Key must not be null");
235228

236229
return new KeyBoundCursor<Entry<byte[], byte[]>>(key, cursorId, options) {
237230

238231
@Override
239-
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId, ScanOptions options) {
232+
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, CursorId cursorId, ScanOptions options) {
240233

241234
if (isQueueing() || isPipelined()) {
242235
throw new InvalidDataAccessApiUsageException("'HSCAN' cannot be called in pipeline / transaction mode");
243236
}
244237

245238
ScanParams params = JedisConverters.toScanParams(options);
246239

247-
ScanResult<Entry<byte[], byte[]>> result = connection.getJedis().hscan(key, JedisConverters.toBytes(cursorId),
248-
params);
249-
return new ScanIteration<>(Long.valueOf(result.getCursor()), result.getResult());
240+
ScanResult<Entry<byte[], byte[]>> result = connection.getJedis().hscan(key,
241+
JedisConverters.toBytes(cursorId), params);
242+
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
250243
}
251244

252245
@Override

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
3737
import org.springframework.data.redis.connection.convert.Converters;
3838
import org.springframework.data.redis.core.Cursor;
39+
import org.springframework.data.redis.core.Cursor.CursorId;
3940
import org.springframework.data.redis.core.KeyScanOptions;
4041
import org.springframework.data.redis.core.ScanCursor;
4142
import org.springframework.data.redis.core.ScanIteration;
@@ -131,7 +132,7 @@ public Set<byte[]> keys(byte[] pattern) {
131132

132133
@Override
133134
public Cursor<byte[]> scan(ScanOptions options) {
134-
return scan(0, options != null ? options : ScanOptions.NONE);
135+
return scan(CursorId.initial(), options != null ? options : ScanOptions.NONE);
135136
}
136137

137138
/**
@@ -140,12 +141,12 @@ public Cursor<byte[]> scan(ScanOptions options) {
140141
* @param options
141142
* @return
142143
*/
143-
public Cursor<byte[]> scan(long cursorId, ScanOptions options) {
144+
public Cursor<byte[]> scan(CursorId cursorId, ScanOptions options) {
144145

145146
return new ScanCursor<byte[]>(cursorId, options) {
146147

147148
@Override
148-
protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
149+
protected ScanIteration<byte[]> doScan(CursorId cursorId, ScanOptions options) {
149150

150151
if (isQueueing() || isPipelined()) {
151152
throw new InvalidDataAccessApiUsageException("'SCAN' cannot be called in pipeline / transaction mode");
@@ -165,12 +166,12 @@ protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
165166
}
166167

167168
if (type != null) {
168-
result = connection.getJedis().scan(Long.toString(cursorId).getBytes(), params, type);
169+
result = connection.getJedis().scan(JedisConverters.toBytes(cursorId), params, type);
169170
} else {
170-
result = connection.getJedis().scan(Long.toString(cursorId).getBytes(), params);
171+
result = connection.getJedis().scan(JedisConverters.toBytes(cursorId), params);
171172
}
172173

173-
return new ScanIteration<>(Long.parseLong(result.getCursor()), result.getResult());
174+
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
174175
}
175176

176177
protected void doClose() {

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.dao.InvalidDataAccessApiUsageException;
2828
import org.springframework.data.redis.connection.RedisSetCommands;
2929
import org.springframework.data.redis.core.Cursor;
30+
import org.springframework.data.redis.core.Cursor.CursorId;
3031
import org.springframework.data.redis.core.KeyBoundCursor;
3132
import org.springframework.data.redis.core.ScanIteration;
3233
import org.springframework.data.redis.core.ScanOptions;
@@ -206,24 +207,24 @@ public Long sUnionStore(byte[] destKey, byte[]... keys) {
206207

207208
@Override
208209
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
209-
return sScan(key, 0, options);
210+
return sScan(key, CursorId.initial(), options);
210211
}
211212

212213
/**
213-
* @since 1.4
214214
* @param key
215215
* @param cursorId
216216
* @param options
217217
* @return
218+
* @since 3.2.1
218219
*/
219-
public Cursor<byte[]> sScan(byte[] key, long cursorId, ScanOptions options) {
220+
public Cursor<byte[]> sScan(byte[] key, CursorId cursorId, ScanOptions options) {
220221

221222
Assert.notNull(key, "Key must not be null");
222223

223224
return new KeyBoundCursor<byte[]>(key, cursorId, options) {
224225

225226
@Override
226-
protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions options) {
227+
protected ScanIteration<byte[]> doScan(byte[] key, CursorId cursorId, ScanOptions options) {
227228

228229
if (isQueueing() || isPipelined()) {
229230
throw new InvalidDataAccessApiUsageException("'SSCAN' cannot be called in pipeline / transaction mode");
@@ -232,7 +233,7 @@ protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions op
232233
ScanParams params = JedisConverters.toScanParams(options);
233234

234235
ScanResult<byte[]> result = connection.getJedis().sscan(key, JedisConverters.toBytes(cursorId), params);
235-
return new ScanIteration<>(Long.valueOf(result.getCursor()), result.getResult());
236+
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
236237
}
237238

238239
protected void doClose() {

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.data.redis.connection.zset.Tuple;
3636
import org.springframework.data.redis.connection.zset.Weights;
3737
import org.springframework.data.redis.core.Cursor;
38+
import org.springframework.data.redis.core.Cursor.CursorId;
3839
import org.springframework.data.redis.core.KeyBoundCursor;
3940
import org.springframework.data.redis.core.ScanIteration;
4041
import org.springframework.data.redis.core.ScanOptions;
@@ -561,24 +562,25 @@ public Long zUnionStore(byte[] destKey, byte[]... sets) {
561562

562563
@Override
563564
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
564-
return zScan(key, 0L, options);
565+
return zScan(key, CursorId.initial(), options);
565566
}
566567

568+
567569
/**
568-
* @since 1.4
569570
* @param key
570571
* @param cursorId
571572
* @param options
572573
* @return
574+
* @since 3.2.1
573575
*/
574-
public Cursor<Tuple> zScan(byte[] key, Long cursorId, ScanOptions options) {
576+
public Cursor<Tuple> zScan(byte[] key, CursorId cursorId, ScanOptions options) {
575577

576578
Assert.notNull(key, "Key must not be null");
577579

578580
return new KeyBoundCursor<Tuple>(key, cursorId, options) {
579581

580582
@Override
581-
protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions options) {
583+
protected ScanIteration<Tuple> doScan(byte[] key, CursorId cursorId, ScanOptions options) {
582584

583585
if (isQueueing() || isPipelined()) {
584586
throw new InvalidDataAccessApiUsageException("'ZSCAN' cannot be called in pipeline / transaction mode");
@@ -588,7 +590,7 @@ protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions opt
588590

589591
ScanResult<redis.clients.jedis.resps.Tuple> result = connection.getJedis().zscan(key,
590592
JedisConverters.toBytes(cursorId), params);
591-
return new ScanIteration<>(Long.valueOf(result.getCursor()),
593+
return new ScanIteration<>(CursorId.of(result.getCursor()),
592594
JedisConverters.tuplesToTuples().convert(result.getResult()));
593595
}
594596

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
import org.apache.commons.logging.Log;
5959
import org.apache.commons.logging.LogFactory;
60+
6061
import org.springframework.beans.BeanUtils;
6162
import org.springframework.core.convert.converter.Converter;
6263
import org.springframework.dao.DataAccessException;
@@ -70,6 +71,7 @@
7071
import org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider.TargetAware;
7172
import org.springframework.data.redis.connection.lettuce.LettuceResult.LettuceResultBuilder;
7273
import org.springframework.data.redis.connection.lettuce.LettuceResult.LettuceStatusResult;
74+
import org.springframework.data.redis.core.Cursor.CursorId;
7375
import org.springframework.data.redis.core.RedisCommand;
7476
import org.springframework.lang.Nullable;
7577
import org.springframework.util.Assert;
@@ -1060,8 +1062,8 @@ private void potentiallySelectDatabase(int dbIndex) {
10601062
}
10611063
}
10621064

1063-
io.lettuce.core.ScanCursor getScanCursor(long cursorId) {
1064-
return io.lettuce.core.ScanCursor.of(Long.toString(cursorId));
1065+
io.lettuce.core.ScanCursor getScanCursor(CursorId cursorId) {
1066+
return io.lettuce.core.ScanCursor.of(cursorId.getCursorId());
10651067
}
10661068

10671069
private void validateCommandIfRunningInTransactionMode(ProtocolKeyword cmd, byte[]... args) {

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.redis.connection.RedisHashCommands;
3030
import org.springframework.data.redis.connection.convert.Converters;
3131
import org.springframework.data.redis.core.Cursor;
32+
import org.springframework.data.redis.core.Cursor.CursorId;
3233
import org.springframework.data.redis.core.KeyBoundCursor;
3334
import org.springframework.data.redis.core.ScanIteration;
3435
import org.springframework.data.redis.core.ScanOptions;
@@ -204,24 +205,25 @@ public List<byte[]> hVals(byte[] key) {
204205

205206
@Override
206207
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
207-
return hScan(key, 0, options);
208+
return hScan(key, CursorId.initial(), options);
208209
}
209210

211+
210212
/**
211-
* @since 1.4
212213
* @param key
213214
* @param cursorId
214215
* @param options
215216
* @return
217+
* @since 1.4
216218
*/
217-
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, long cursorId, ScanOptions options) {
219+
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, CursorId cursorId, ScanOptions options) {
218220

219221
Assert.notNull(key, "Key must not be null");
220222

221223
return new KeyBoundCursor<Entry<byte[], byte[]>>(key, cursorId, options) {
222224

223225
@Override
224-
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId, ScanOptions options) {
226+
protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, CursorId cursorId, ScanOptions options) {
225227

226228
if (connection.isQueueing() || connection.isPipelined()) {
227229
throw new InvalidDataAccessApiUsageException("'HSCAN' cannot be called in pipeline / transaction mode");
@@ -235,7 +237,7 @@ protected ScanIteration<Entry<byte[], byte[]>> doScan(byte[] key, long cursorId,
235237
String nextCursorId = mapScanCursor.getCursor();
236238

237239
Map<byte[], byte[]> values = mapScanCursor.getMap();
238-
return new ScanIteration<>(Long.valueOf(nextCursorId), values.entrySet());
240+
return new ScanIteration<>(CursorId.of(nextCursorId), values.entrySet());
239241
}
240242

241243
@Override

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ abstract class LettuceScanCursor<T> extends ScanCursor<T> {
4646
}
4747

4848
@Override
49-
protected ScanIteration<T> doScan(long cursorId, ScanOptions options) {
49+
protected ScanIteration<T> doScan(CursorId cursorId, ScanOptions options) {
5050

51-
if (state == null && cursorId == 0) {
51+
if (state == null && cursorId.isInitial()) {
5252
return scanAndProcessState(io.lettuce.core.ScanCursor.INITIAL, options);
5353
}
5454

@@ -64,7 +64,7 @@ protected ScanIteration<T> doScan(long cursorId, ScanOptions options) {
6464
}
6565

6666
@Override
67-
protected boolean isFinished(long cursorId) {
67+
protected boolean isFinished(CursorId cursorId) {
6868
return state != null && isMatchingCursor(cursorId) ? state.isFinished() : super.isFinished(cursorId);
6969
}
7070

@@ -76,8 +76,8 @@ private ScanIteration<T> scanAndProcessState(io.lettuce.core.ScanCursor scanCurs
7676
return iteration;
7777
}
7878

79-
private boolean isMatchingCursor(long cursorId) {
80-
return state != null && state.getCursor().equals(Long.toString(cursorId));
79+
private boolean isMatchingCursor(CursorId cursorId) {
80+
return state != null && state.getCursor().equals(cursorId.getCursorId());
8181
}
8282

8383
/**
@@ -101,7 +101,7 @@ static class LettuceScanIteration<T> extends ScanIteration<T> {
101101

102102
LettuceScanIteration(io.lettuce.core.ScanCursor cursor, Collection<T> items) {
103103

104-
super(Long.parseLong(cursor.getCursor()), items);
104+
super(CursorId.of(cursor.getCursor()), items);
105105
this.cursor = cursor;
106106
}
107107
}

0 commit comments

Comments
 (0)