Skip to content

Commit

Permalink
A few Redis usability improvements
Browse files Browse the repository at this point in the history
- Add support for ts.add(key, val, args) - without timestamp
- Add support for Jackson Polymorphic serialization/deserialization
- Fix NPE when using empty sorted set
  • Loading branch information
cescoffier committed Mar 21, 2023
1 parent 187ca4b commit af30f2c
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public int increment() {
return (int) commands.incrby("counter-dev-mode", INCREMENT);
}

@GET
@Path("/val")
public int value() {
return commands.get("counter-dev-mode");
}

@GET
@Path("/keys")
public int verifyPreloading() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import java.util.function.Function;
import java.util.function.Supplier;

import org.awaitility.Awaitility;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

Expand Down Expand Up @@ -47,6 +49,10 @@ public String apply(String s) {
return s.replace("INCREMENT = 1", "INCREMENT = 10");
}
});

Awaitility.await()
.untilAsserted(() -> Assertions.assertEquals("2", RestAssured.get("/inc/val").andReturn().asString()));

RestAssured.get("/inc")
.then()
.statusCode(200)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ public interface ReactiveTimeSeriesCommands<K> extends ReactiveRedisCommands {
**/
Uni<Void> tsAdd(K key, double value);

/**
* Execute the command <a href="https://redis.io/commands/ts.add/">TS.ADD</a>.
* Summary: Append a sample to a time series
* Group: time series
* <p>
* Unlike {@link #tsAdd(Object, long, double, AddArgs)}, set the timestamp according to the server clock.
*
* @param key the key name for the time series must not be {@code null}
* @param value the numeric data value of the sample.
* @param args the creation arguments.
* @return A uni emitting {@code null} when the operation completes
*/
Uni<Void> tsAdd(K key, double value, AddArgs args);

/**
* Execute the command <a href="https://redis.io/commands/ts.alter/">TS.ALTER</a>.
* Summary: Update the retention, chunk size, duplicate policy, and labels of an existing time series
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ public interface TimeSeriesCommands<K> extends RedisCommands {
*/
void tsAdd(K key, double value);

/**
* Execute the command <a href="https://redis.io/commands/ts.add/">TS.ADD</a>.
* Summary: Append a sample to a time series
* Group: time series
* <p>
* Unlike {@link #tsAdd(Object, long, double, AddArgs)}, set the timestamp according to the server clock.
*
* @param key the key name for the time series must not be {@code null}
* @param value the numeric data value of the sample.
* @param args the creation arguments.
*/
void tsAdd(K key, double value, AddArgs args);

/**
* Execute the command <a href="https://redis.io/commands/ts.alter/">TS.ALTER</a>.
* Summary: Update the retention, chunk size, duplicate policy, and labels of an existing time series
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ protected String getScoreAsString(double score) {

final List<ScoredValue<V>> decodeAsListOfScoredValues(Response response) {
List<ScoredValue<V>> list = new ArrayList<>();
if (!response.iterator().hasNext()) {
if (response == null || !response.iterator().hasNext()) {
return Collections.emptyList();
}
if (response.iterator().next().type() == ResponseType.BULK) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ Uni<Response> _tsAdd(K key, double value) {
return execute(cmd);
}

Uni<Response> _tsAdd(K key, double value, AddArgs args) {
nonNull(key, "key");
nonNull(args, "args");
RedisCommand cmd = RedisCommand.of(Command.TS_ADD).put(marshaller.encode(key)).put("*").put(value).putArgs(args);
return execute(cmd);
}

Uni<Response> _tsAlter(K key, AlterArgs args) {
nonNull(key, "key");
nonNull(args, "args");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public void tsAdd(K key, long timestamp, double value, AddArgs args) {
reactive.tsAdd(key, timestamp, value, args).await().atMost(timeout);
}

@Override
public void tsAdd(K key, double value, AddArgs args) {
reactive.tsAdd(key, value, args).await().atMost(timeout);
}

@Override
public void tsAdd(K key, long timestamp, double value) {
reactive.tsAdd(key, timestamp, value).await().atMost(timeout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import io.quarkus.redis.datasource.codecs.Codec;
Expand All @@ -25,13 +25,13 @@ public class Marshaller {
private static final Map<Class<?>, Codec<?>> DEFAULT_CODECS;

static {
DEFAULT_CODECS = new HashMap<>();
DEFAULT_CODECS.put(String.class, Codecs.StringCodec.INSTANCE);
DEFAULT_CODECS.put(Integer.class, Codecs.IntegerCodec.INSTANCE);
DEFAULT_CODECS.put(Double.class, Codecs.DoubleCodec.INSTANCE);
DEFAULT_CODECS = Map.of(
String.class, Codecs.StringCodec.INSTANCE,
Integer.class, Codecs.IntegerCodec.INSTANCE,
Double.class, Codecs.DoubleCodec.INSTANCE);
}

Map<Class<?>, Codec<?>> codecs = new HashMap<>();
Map<Class<?>, Codec<?>> codecs = new ConcurrentHashMap<>();

public Marshaller(Class<?>... hints) {
doesNotContainNull(hints, "hints");
Expand All @@ -51,11 +51,12 @@ public byte[] encode(Object o) {
}
Class<?> clazz = o.getClass();
Codec codec = codec(clazz);
if (codec != null) {
return codec.encode(o);
} else {
throw new IllegalArgumentException("Unable to encode object of type " + clazz);
if (codec == null) {
// Default to JSON.
codec = new Codecs.JsonCodec<>(clazz);
codecs.put(clazz, codec);
}
return codec.encode(o);
}

@SafeVarargs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ public Uni<Void> tsAdd(K key, long timestamp, double value, AddArgs args) {
.replaceWithVoid();
}

@Override
public Uni<Void> tsAdd(K key, double value, AddArgs args) {
return super._tsAdd(key, value, args)
.replaceWithVoid();
}

@Override
public Uni<Void> tsAdd(K key, long timestamp, double value) {
return super._tsAdd(key, timestamp, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import io.quarkus.redis.datasource.list.KeyValue;
import io.quarkus.redis.datasource.list.LPosArgs;
import io.quarkus.redis.datasource.list.ListCommands;
Expand Down Expand Up @@ -352,4 +356,80 @@ void sort() {
assertThat(commands.lpop("dest2", 100)).containsExactly("1", "2", "3", "4", "5", "5", "6", "7", "8", "9");
}

@Test
void testJacksonPolymorphism() {
var cmd = ds.list(Animal.class);

var cat = new Cat();
cat.setId("1234");
cat.setName("the cat");

var rabbit = new Rabbit();
rabbit.setName("roxanne");
rabbit.setColor("grey");

cmd.lpush(key, cat, rabbit);

var shouldBeACat = cmd.rpop(key);
var shouldBeARabbit = cmd.rpop(key);

assertThat(shouldBeACat).isInstanceOf(Cat.class)
.satisfies(animal -> {
assertThat(animal.getName()).isEqualTo("the cat");
assertThat(((Cat) animal).getId()).isEqualTo("1234");
});

assertThat(shouldBeARabbit).isInstanceOf(Rabbit.class)
.satisfies(animal -> {
assertThat(animal.getName()).isEqualTo("roxanne");
assertThat(((Rabbit) animal).getColor()).isEqualTo("grey");
});
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "Cat"),
@JsonSubTypes.Type(value = Rabbit.class, name = "Rabbit")
})
public static class Animal {

private String name;

public String getName() {
return name;
}

public Animal setName(String name) {
this.name = name;
return this;
}
}

public static class Rabbit extends Animal {

private String color;

public String getColor() {
return color;
}

public Rabbit setColor(String color) {
this.color = color;
return this;
}
}

public static class Cat extends Animal {
private String id;

public String getId() {
return id;
}

public Cat setId(String id) {
this.id = id;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ void zrevrange() {
@RequiresRedis6OrHigher
void zrevrangeWithScoreEmpty() {
assertThat(ds.sortedSet(String.class).zrangeWithScores("top-products", 0, 2, new ZRangeArgs().rev())).isEmpty();
assertThat(ds.sortedSet(String.class).zrangeWithScores("missing", 0, 2)).isEmpty();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ void testCreationAndAddingDataPoint() throws InterruptedException {
assertThat(list).hasSize(1);
}

@Test
void testAdd() throws InterruptedException {
ts.tsAdd(key, 25.0);
Thread.sleep(10); // Make sure the timestamp is different
ts.tsAdd(key, 30.5, new AddArgs().label("foo", "bar"));
var list = ts.tsRange(key, TimeSeriesRange.fromTimeSeries());
assertThat(list).hasSize(2);
}

@Test
void testCreationWhileAdding() {
long timestamp = System.currentTimeMillis() - 1000;
Expand Down

0 comments on commit af30f2c

Please sign in to comment.