Skip to content

Commit

Permalink
Adding support for scan and flushdb commands. (#38)
Browse files Browse the repository at this point in the history
* Adding support for `scan` and `flushdb` commands.

* Using Java Optional instead of returning null
  • Loading branch information
davidkus authored and fppt committed Apr 11, 2019
1 parent 71ed7df commit f0aa3d5
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 29 deletions.
28 changes: 28 additions & 0 deletions src/main/java/com/github/fppt/jedismock/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,32 @@ public static int convertToInteger(String value){
throw new WrongValueTypeException("ERR bit offset is not an integer or out of range");
}
}

public static String createRegexFromGlob(String glob)
{
StringBuilder out = new StringBuilder("^");
for(int i = 0; i < glob.length(); ++i)
{
final char c = glob.charAt(i);
switch(c)
{
case '*':
out.append(".*");
break;
case '?':
out.append('.');
break;
case '.':
out.append("\\.");
break;
case '\\':
out.append("\\\\");
break;
default:
out.append(c);
}
}
out.append('$');
return out.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ public class OperationFactory {
TRANSACTIONAL_OPERATIONS.put("brpoplpush", RO_brpoplpush::new);
TRANSACTIONAL_OPERATIONS.put("publish", RO_publish::new);
TRANSACTIONAL_OPERATIONS.put("flushall", RO_flushall::new);
TRANSACTIONAL_OPERATIONS.put("flushdb", RO_flushdb::new);
TRANSACTIONAL_OPERATIONS.put("lrem", RO_lrem::new);
TRANSACTIONAL_OPERATIONS.put("ping", RO_ping::new);
TRANSACTIONAL_OPERATIONS.put("keys", RO_keys::new);
TRANSACTIONAL_OPERATIONS.put("sadd", RO_sadd::new);
TRANSACTIONAL_OPERATIONS.put("scan", RO_scan::new);
TRANSACTIONAL_OPERATIONS.put("spop", RO_spop::new);
TRANSACTIONAL_OPERATIONS.put("srem", RO_srem::new);
TRANSACTIONAL_OPERATIONS.put("scard", RO_scard::new);
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/github/fppt/jedismock/operations/RO_flushdb.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.github.fppt.jedismock.operations;

import com.github.fppt.jedismock.server.Response;
import com.github.fppt.jedismock.server.Slice;
import com.github.fppt.jedismock.storage.RedisBase;

import java.util.List;

class RO_flushdb extends AbstractRedisOperation {
RO_flushdb(RedisBase base, List<Slice> params) {
super(base, params);
}

Slice response(){
base().clear();
return Response.OK;
}
}
31 changes: 2 additions & 29 deletions src/main/java/com/github/fppt/jedismock/operations/RO_keys.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.fppt.jedismock.operations;

import com.github.fppt.jedismock.Utils;
import com.github.fppt.jedismock.server.Response;
import com.github.fppt.jedismock.server.Slice;
import com.github.fppt.jedismock.storage.RedisBase;
Expand All @@ -14,7 +15,7 @@ class RO_keys extends AbstractRedisOperation {

Slice response() {
List<Slice> matchingKeys = new ArrayList<>();
String regex = createRegexFromGlob(new String(params().get(0).data()));
String regex = Utils.createRegexFromGlob(new String(params().get(0).data()));

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

return Response.array(matchingKeys);
}

private static String createRegexFromGlob(String glob)
{
StringBuilder out = new StringBuilder("^");
for(int i = 0; i < glob.length(); ++i)
{
final char c = glob.charAt(i);
switch(c)
{
case '*':
out.append(".*");
break;
case '?':
out.append('.');
break;
case '.':
out.append("\\.");
break;
case '\\':
out.append("\\\\");
break;
default:
out.append(c);
}
}
out.append('$');
return out.toString();
}
}
62 changes: 62 additions & 0 deletions src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.github.fppt.jedismock.operations;

import com.github.fppt.jedismock.Utils;
import com.github.fppt.jedismock.server.Response;
import com.github.fppt.jedismock.server.Slice;
import com.github.fppt.jedismock.storage.RedisBase;
import com.google.common.collect.Lists;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.github.fppt.jedismock.Utils.convertToLong;

class RO_scan extends AbstractRedisOperation {

private static final long CURSOR_START = 0;
// From the Redis documentation, the default count if not specified:
private static final long DEFAULT_COUNT = 10;

private static final String MATCH = "match";
private static final String COUNT = "count";

RO_scan(RedisBase base, List<Slice> params) {
super(base, params);
}

Slice response() {

Slice cursorSlice = params().get(0);
long cursor = cursorSlice != null ? convertToLong(cursorSlice.toString()) : CURSOR_START;

String match = extractParameter(params(), MATCH).map(Slice::toString).orElse("*");
long count = extractParameter(params(), COUNT).map(s -> convertToLong(s.toString())).orElse(DEFAULT_COUNT);

String regex = Utils.createRegexFromGlob(match);
List<Slice> matchingKeys = base().keys().stream()
.skip(cursor)
.limit(count)
.filter(x -> x.toString().matches(regex))
.map(Response::bulkString)
.collect(Collectors.toList());

cursor = cursor + count;
if (cursor >= base().keys().size()) {
cursor = CURSOR_START;
}

List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingKeys));
return Response.array(response);
}

private static Optional<Slice> extractParameter(List<Slice> params, String name) {
for (int i = 0; i < params.size(); i++) {
String param = new String(params.get(i).data());
if (name.equalsIgnoreCase(param)) {
return Optional.of(params.get(i + 1));
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.junit.runner.RunWith;
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.exceptions.JedisConnectionException;

import java.util.*;
Expand All @@ -17,6 +19,7 @@
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

Expand Down Expand Up @@ -99,6 +102,18 @@ public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis){
assertNull(jedis.get(key));
}

@Theory
public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis){
String key = "my-super-special-key";
String value = "my-not-so-special-value";

jedis.set(key, value);
assertEquals(value, jedis.get(key));

jedis.flushDB();
assertNull(jedis.get(key));
}

@Theory
public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis){
String key = "my-super-special-sexy-key";
Expand Down Expand Up @@ -424,4 +439,56 @@ public void timeReturnsCurrentTime(Jedis jedis) {
//Microseconds are correct integer value
Long.parseLong(time.get(1));
}

@Theory
public void scanReturnsAllKey(Jedis jedis) {

jedis.flushDB();

String key = "scankey:111";
String key2 = "scankey:222";
String value = "myvalue";
jedis.set(key, value);
jedis.set(key2, value);

ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START);

assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
assertEquals(2, result.getResult().size());
assertTrue(result.getResult().contains(key));
assertTrue(result.getResult().contains(key2));
}

@Theory
public void scanReturnsMatchingKey(Jedis jedis) {

jedis.flushDB();

String key = "scankeymatch:111";
String key2 = "scankeymatch:222";
String value = "myvalue";
jedis.set(key, value);
jedis.set(key2, value);

ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START, new ScanParams().match("scankeymatch:1*"));

assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
assertEquals(1, result.getResult().size());
assertTrue(result.getResult().contains(key));
}

@Theory
public void scanIterates(Jedis jedis) {

jedis.flushDB();

String value = "myvalue";
for (int i = 0; i < 20; i++) {
jedis.set("scankeyi:" + i, value);
}

ScanResult<String> result = jedis.scan(ScanParams.SCAN_POINTER_START, new ScanParams().match("scankeyi:1*").count(10));

assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
}
}

0 comments on commit f0aa3d5

Please sign in to comment.