Skip to content

Commit f0aa3d5

Browse files
davidkusfppt
authored andcommitted
Adding support for scan and flushdb commands. (#38)
* Adding support for `scan` and `flushdb` commands. * Using Java Optional instead of returning null
1 parent 71ed7df commit f0aa3d5

File tree

6 files changed

+179
-29
lines changed

6 files changed

+179
-29
lines changed

src/main/java/com/github/fppt/jedismock/Utils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,32 @@ public static int convertToInteger(String value){
8787
throw new WrongValueTypeException("ERR bit offset is not an integer or out of range");
8888
}
8989
}
90+
91+
public static String createRegexFromGlob(String glob)
92+
{
93+
StringBuilder out = new StringBuilder("^");
94+
for(int i = 0; i < glob.length(); ++i)
95+
{
96+
final char c = glob.charAt(i);
97+
switch(c)
98+
{
99+
case '*':
100+
out.append(".*");
101+
break;
102+
case '?':
103+
out.append('.');
104+
break;
105+
case '.':
106+
out.append("\\.");
107+
break;
108+
case '\\':
109+
out.append("\\\\");
110+
break;
111+
default:
112+
out.append(c);
113+
}
114+
}
115+
out.append('$');
116+
return out.toString();
117+
}
90118
}

src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ public class OperationFactory {
5252
TRANSACTIONAL_OPERATIONS.put("brpoplpush", RO_brpoplpush::new);
5353
TRANSACTIONAL_OPERATIONS.put("publish", RO_publish::new);
5454
TRANSACTIONAL_OPERATIONS.put("flushall", RO_flushall::new);
55+
TRANSACTIONAL_OPERATIONS.put("flushdb", RO_flushdb::new);
5556
TRANSACTIONAL_OPERATIONS.put("lrem", RO_lrem::new);
5657
TRANSACTIONAL_OPERATIONS.put("ping", RO_ping::new);
5758
TRANSACTIONAL_OPERATIONS.put("keys", RO_keys::new);
5859
TRANSACTIONAL_OPERATIONS.put("sadd", RO_sadd::new);
60+
TRANSACTIONAL_OPERATIONS.put("scan", RO_scan::new);
5961
TRANSACTIONAL_OPERATIONS.put("spop", RO_spop::new);
6062
TRANSACTIONAL_OPERATIONS.put("srem", RO_srem::new);
6163
TRANSACTIONAL_OPERATIONS.put("scard", RO_scard::new);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.github.fppt.jedismock.operations;
2+
3+
import com.github.fppt.jedismock.server.Response;
4+
import com.github.fppt.jedismock.server.Slice;
5+
import com.github.fppt.jedismock.storage.RedisBase;
6+
7+
import java.util.List;
8+
9+
class RO_flushdb extends AbstractRedisOperation {
10+
RO_flushdb(RedisBase base, List<Slice> params) {
11+
super(base, params);
12+
}
13+
14+
Slice response(){
15+
base().clear();
16+
return Response.OK;
17+
}
18+
}
Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.fppt.jedismock.operations;
22

3+
import com.github.fppt.jedismock.Utils;
34
import com.github.fppt.jedismock.server.Response;
45
import com.github.fppt.jedismock.server.Slice;
56
import com.github.fppt.jedismock.storage.RedisBase;
@@ -14,7 +15,7 @@ class RO_keys extends AbstractRedisOperation {
1415

1516
Slice response() {
1617
List<Slice> matchingKeys = new ArrayList<>();
17-
String regex = createRegexFromGlob(new String(params().get(0).data()));
18+
String regex = Utils.createRegexFromGlob(new String(params().get(0).data()));
1819

1920
base().keys().forEach(keyData -> {
2021
String key = new String(keyData.data());
@@ -25,32 +26,4 @@ Slice response() {
2526

2627
return Response.array(matchingKeys);
2728
}
28-
29-
private static String createRegexFromGlob(String glob)
30-
{
31-
StringBuilder out = new StringBuilder("^");
32-
for(int i = 0; i < glob.length(); ++i)
33-
{
34-
final char c = glob.charAt(i);
35-
switch(c)
36-
{
37-
case '*':
38-
out.append(".*");
39-
break;
40-
case '?':
41-
out.append('.');
42-
break;
43-
case '.':
44-
out.append("\\.");
45-
break;
46-
case '\\':
47-
out.append("\\\\");
48-
break;
49-
default:
50-
out.append(c);
51-
}
52-
}
53-
out.append('$');
54-
return out.toString();
55-
}
5629
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.github.fppt.jedismock.operations;
2+
3+
import com.github.fppt.jedismock.Utils;
4+
import com.github.fppt.jedismock.server.Response;
5+
import com.github.fppt.jedismock.server.Slice;
6+
import com.github.fppt.jedismock.storage.RedisBase;
7+
import com.google.common.collect.Lists;
8+
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.stream.Collectors;
12+
13+
import static com.github.fppt.jedismock.Utils.convertToLong;
14+
15+
class RO_scan extends AbstractRedisOperation {
16+
17+
private static final long CURSOR_START = 0;
18+
// From the Redis documentation, the default count if not specified:
19+
private static final long DEFAULT_COUNT = 10;
20+
21+
private static final String MATCH = "match";
22+
private static final String COUNT = "count";
23+
24+
RO_scan(RedisBase base, List<Slice> params) {
25+
super(base, params);
26+
}
27+
28+
Slice response() {
29+
30+
Slice cursorSlice = params().get(0);
31+
long cursor = cursorSlice != null ? convertToLong(cursorSlice.toString()) : CURSOR_START;
32+
33+
String match = extractParameter(params(), MATCH).map(Slice::toString).orElse("*");
34+
long count = extractParameter(params(), COUNT).map(s -> convertToLong(s.toString())).orElse(DEFAULT_COUNT);
35+
36+
String regex = Utils.createRegexFromGlob(match);
37+
List<Slice> matchingKeys = base().keys().stream()
38+
.skip(cursor)
39+
.limit(count)
40+
.filter(x -> x.toString().matches(regex))
41+
.map(Response::bulkString)
42+
.collect(Collectors.toList());
43+
44+
cursor = cursor + count;
45+
if (cursor >= base().keys().size()) {
46+
cursor = CURSOR_START;
47+
}
48+
49+
List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingKeys));
50+
return Response.array(response);
51+
}
52+
53+
private static Optional<Slice> extractParameter(List<Slice> params, String name) {
54+
for (int i = 0; i < params.size(); i++) {
55+
String param = new String(params.get(i).data());
56+
if (name.equalsIgnoreCase(param)) {
57+
return Optional.of(params.get(i + 1));
58+
}
59+
}
60+
return Optional.empty();
61+
}
62+
}

src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.junit.runner.RunWith;
77
import redis.clients.jedis.Client;
88
import redis.clients.jedis.Jedis;
9+
import redis.clients.jedis.ScanParams;
10+
import redis.clients.jedis.ScanResult;
911
import redis.clients.jedis.exceptions.JedisConnectionException;
1012

1113
import java.util.*;
@@ -17,6 +19,7 @@
1719
import static junit.framework.TestCase.assertTrue;
1820
import static org.junit.Assert.assertEquals;
1921
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertNotEquals;
2023
import static org.junit.Assert.assertNotNull;
2124
import static org.junit.Assert.assertNull;
2225

@@ -99,6 +102,18 @@ public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis){
99102
assertNull(jedis.get(key));
100103
}
101104

105+
@Theory
106+
public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis){
107+
String key = "my-super-special-key";
108+
String value = "my-not-so-special-value";
109+
110+
jedis.set(key, value);
111+
assertEquals(value, jedis.get(key));
112+
113+
jedis.flushDB();
114+
assertNull(jedis.get(key));
115+
}
116+
102117
@Theory
103118
public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis){
104119
String key = "my-super-special-sexy-key";
@@ -424,4 +439,56 @@ public void timeReturnsCurrentTime(Jedis jedis) {
424439
//Microseconds are correct integer value
425440
Long.parseLong(time.get(1));
426441
}
442+
443+
@Theory
444+
public void scanReturnsAllKey(Jedis jedis) {
445+
446+
jedis.flushDB();
447+
448+
String key = "scankey:111";
449+
String key2 = "scankey:222";
450+
String value = "myvalue";
451+
jedis.set(key, value);
452+
jedis.set(key2, value);
453+
454+
ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START);
455+
456+
assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
457+
assertEquals(2, result.getResult().size());
458+
assertTrue(result.getResult().contains(key));
459+
assertTrue(result.getResult().contains(key2));
460+
}
461+
462+
@Theory
463+
public void scanReturnsMatchingKey(Jedis jedis) {
464+
465+
jedis.flushDB();
466+
467+
String key = "scankeymatch:111";
468+
String key2 = "scankeymatch:222";
469+
String value = "myvalue";
470+
jedis.set(key, value);
471+
jedis.set(key2, value);
472+
473+
ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START, new ScanParams().match("scankeymatch:1*"));
474+
475+
assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
476+
assertEquals(1, result.getResult().size());
477+
assertTrue(result.getResult().contains(key));
478+
}
479+
480+
@Theory
481+
public void scanIterates(Jedis jedis) {
482+
483+
jedis.flushDB();
484+
485+
String value = "myvalue";
486+
for (int i = 0; i < 20; i++) {
487+
jedis.set("scankeyi:" + i, value);
488+
}
489+
490+
ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START, new ScanParams().match("scankeyi:1*").count(10));
491+
492+
assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
493+
}
427494
}

0 commit comments

Comments
 (0)