Skip to content

Commit

Permalink
Introducing JSON to Lettuce (#2933) (#2992)
Browse files Browse the repository at this point in the history
* Most of the API layer

* Add the JSON.TYPE command

* Kotlin coroutines added;
Started JSON README.md;
Parser registry is now part of the Connection;
formatting;
chained all commands;
extracted commands in their own unit RedisJsonCommandBuilder;

* Implemented 90% of commands from top 10

* All but SET are implemented

* Integrated Jackson, finished up the SET command

* Left out a few files by mistake

* Adding some JavaDoc

* Formatting

* Implemented all JSON commands

* Introducing test containers to the testing fw

* Complete coverage with integration tests, straight scenarios

* Added Pathv1 tests

* Added RedisCE cluster support for the JSON.MGET and JSON.MSET commands

* Handle null values

* No longer using K for the JSON object keys

* Polishing

* JsonType introduced to help typization

* Remove the RedisCodec from the JsonValue/JsonParser abstraction, add configuration for custom parsers

* Extend API surface with methods that reduce the amount of required arguments

* Adding unit tests, addressing changes to README.md

* Implemented object-mapping functionality

* Addresses Ali's comments

* Addressed last bunch of comments from Ali, changed ports to not colide with existing infra

* Forgot to change ports and stop debug log

* Polishing touches
  • Loading branch information
tishun authored Sep 25, 2024
1 parent 2267bfe commit 547271c
Show file tree
Hide file tree
Showing 121 changed files with 8,673 additions and 311 deletions.
23 changes: 23 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<brave.version>5.13.11</brave.version>
<commons-lang3.version>3.13.0</commons-lang3.version>
<commons-pool.version>2.12.0</commons-pool.version>
<jackson-version>2.17.0</jackson-version>
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
<junit5.version>5.10.2</junit5.version>
Expand Down Expand Up @@ -238,6 +239,13 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
<optional>true</optional>
</dependency>

<!-- OS-native transports -->

<dependency>
Expand Down Expand Up @@ -523,6 +531,21 @@
<scope>test</scope>
</dependency>

<!-- TEST CONTAINERS -->

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down
193 changes: 191 additions & 2 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
import io.lettuce.core.codec.Base16;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.json.JsonParser;
import io.lettuce.core.json.JsonType;
import io.lettuce.core.json.JsonValue;
import io.lettuce.core.json.arguments.JsonGetArgs;
import io.lettuce.core.json.arguments.JsonMsetArgs;
import io.lettuce.core.json.JsonPath;
import io.lettuce.core.json.arguments.JsonRangeArgs;
import io.lettuce.core.json.arguments.JsonSetArgs;
import io.lettuce.core.models.stream.ClaimedMessages;
import io.lettuce.core.models.stream.PendingMessage;
import io.lettuce.core.models.stream.PendingMessages;
Expand Down Expand Up @@ -71,21 +79,27 @@ public abstract class AbstractRedisAsyncCommands<K, V> implements RedisAclAsyncC
RedisKeyAsyncCommands<K, V>, RedisStringAsyncCommands<K, V>, RedisListAsyncCommands<K, V>, RedisSetAsyncCommands<K, V>,
RedisSortedSetAsyncCommands<K, V>, RedisScriptingAsyncCommands<K, V>, RedisServerAsyncCommands<K, V>,
RedisHLLAsyncCommands<K, V>, BaseRedisAsyncCommands<K, V>, RedisTransactionalAsyncCommands<K, V>,
RedisGeoAsyncCommands<K, V>, RedisClusterAsyncCommands<K, V> {
RedisGeoAsyncCommands<K, V>, RedisClusterAsyncCommands<K, V>, RedisJsonAsyncCommands<K, V> {

private final StatefulConnection<K, V> connection;

private final RedisCommandBuilder<K, V> commandBuilder;

private final RedisJsonCommandBuilder<K, V> jsonCommandBuilder;

private final JsonParser parser;

/**
* Initialize a new instance.
*
* @param connection the connection to operate on
* @param codec the codec for command encoding
*/
public AbstractRedisAsyncCommands(StatefulConnection<K, V> connection, RedisCodec<K, V> codec) {
public AbstractRedisAsyncCommands(StatefulConnection<K, V> connection, RedisCodec<K, V> codec, JsonParser parser) {
this.parser = parser;
this.connection = connection;
this.commandBuilder = new RedisCommandBuilder<>(codec);
this.jsonCommandBuilder = new RedisJsonCommandBuilder<>(codec, parser);
}

@Override
Expand Down Expand Up @@ -1453,6 +1467,176 @@ public boolean isOpen() {
return connection.isOpen();
}

@Override
public RedisFuture<List<Long>> jsonArrappend(K key, JsonPath jsonPath, JsonValue... values) {
return dispatch(jsonCommandBuilder.jsonArrappend(key, jsonPath, values));
}

@Override
public RedisFuture<List<Long>> jsonArrappend(K key, JsonValue... values) {
return dispatch(jsonCommandBuilder.jsonArrappend(key, JsonPath.ROOT_PATH, values));
}

@Override
public RedisFuture<List<Long>> jsonArrindex(K key, JsonPath jsonPath, JsonValue value, JsonRangeArgs range) {
return dispatch(jsonCommandBuilder.jsonArrindex(key, jsonPath, value, range));
}

@Override
public RedisFuture<List<Long>> jsonArrindex(K key, JsonPath jsonPath, JsonValue value) {
return dispatch(jsonCommandBuilder.jsonArrindex(key, jsonPath, value, JsonRangeArgs.Builder.defaults()));
}

@Override
public RedisFuture<List<Long>> jsonArrinsert(K key, JsonPath jsonPath, int index, JsonValue... values) {
return dispatch(jsonCommandBuilder.jsonArrinsert(key, jsonPath, index, values));
}

@Override
public RedisFuture<List<Long>> jsonArrlen(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonArrlen(key, jsonPath));
}

@Override
public RedisFuture<List<Long>> jsonArrlen(K key) {
return dispatch(jsonCommandBuilder.jsonArrlen(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<List<JsonValue>> jsonArrpop(K key, JsonPath jsonPath, int index) {
return dispatch(jsonCommandBuilder.jsonArrpop(key, jsonPath, index));
}

@Override
public RedisFuture<List<JsonValue>> jsonArrpop(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonArrpop(key, jsonPath, -1));
}

@Override
public RedisFuture<List<JsonValue>> jsonArrpop(K key) {
return dispatch(jsonCommandBuilder.jsonArrpop(key, JsonPath.ROOT_PATH, -1));
}

@Override
public RedisFuture<List<Long>> jsonArrtrim(K key, JsonPath jsonPath, JsonRangeArgs range) {
return dispatch(jsonCommandBuilder.jsonArrtrim(key, jsonPath, range));
}

@Override
public RedisFuture<Long> jsonClear(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonClear(key, jsonPath));
}

@Override
public RedisFuture<Long> jsonClear(K key) {
return dispatch(jsonCommandBuilder.jsonClear(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<Long> jsonDel(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonDel(key, jsonPath));
}

@Override
public RedisFuture<Long> jsonDel(K key) {
return dispatch(jsonCommandBuilder.jsonDel(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<List<JsonValue>> jsonGet(K key, JsonGetArgs options, JsonPath... jsonPaths) {
return dispatch(jsonCommandBuilder.jsonGet(key, options, jsonPaths));
}

@Override
public RedisFuture<List<JsonValue>> jsonGet(K key, JsonPath... jsonPaths) {
return dispatch(jsonCommandBuilder.jsonGet(key, JsonGetArgs.Builder.defaults(), jsonPaths));
}

@Override
public RedisFuture<String> jsonMerge(K key, JsonPath jsonPath, JsonValue value) {
return dispatch(jsonCommandBuilder.jsonMerge(key, jsonPath, value));
}

@Override
public RedisFuture<List<JsonValue>> jsonMGet(JsonPath jsonPath, K... keys) {
return dispatch(jsonCommandBuilder.jsonMGet(jsonPath, keys));
}

@Override
public RedisFuture<String> jsonMSet(List<JsonMsetArgs<K, V>> arguments) {
return dispatch(jsonCommandBuilder.jsonMSet(arguments));
}

@Override
public RedisFuture<List<Number>> jsonNumincrby(K key, JsonPath jsonPath, Number number) {
return dispatch(jsonCommandBuilder.jsonNumincrby(key, jsonPath, number));
}

@Override
public RedisFuture<List<V>> jsonObjkeys(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonObjkeys(key, jsonPath));
}

@Override
public RedisFuture<List<V>> jsonObjkeys(K key) {
return dispatch(jsonCommandBuilder.jsonObjkeys(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<List<Long>> jsonObjlen(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonObjlen(key, jsonPath));
}

@Override
public RedisFuture<List<Long>> jsonObjlen(K key) {
return dispatch(jsonCommandBuilder.jsonObjlen(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<String> jsonSet(K key, JsonPath jsonPath, JsonValue value, JsonSetArgs options) {
return dispatch(jsonCommandBuilder.jsonSet(key, jsonPath, value, options));
}

@Override
public RedisFuture<String> jsonSet(K key, JsonPath jsonPath, JsonValue value) {
return dispatch(jsonCommandBuilder.jsonSet(key, jsonPath, value, JsonSetArgs.Builder.defaults()));
}

@Override
public RedisFuture<List<Long>> jsonStrappend(K key, JsonPath jsonPath, JsonValue value) {
return dispatch(jsonCommandBuilder.jsonStrappend(key, jsonPath, value));
}

@Override
public RedisFuture<List<Long>> jsonStrappend(K key, JsonValue value) {
return dispatch(jsonCommandBuilder.jsonStrappend(key, JsonPath.ROOT_PATH, value));
}

@Override
public RedisFuture<List<Long>> jsonStrlen(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonStrlen(key, jsonPath));
}

@Override
public RedisFuture<List<Long>> jsonStrlen(K key) {
return dispatch(jsonCommandBuilder.jsonStrlen(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<List<Long>> jsonToggle(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonToggle(key, jsonPath));
}

@Override
public RedisFuture<List<JsonType>> jsonType(K key, JsonPath jsonPath) {
return dispatch(jsonCommandBuilder.jsonType(key, jsonPath));
}

@Override
public RedisFuture<List<JsonType>> jsonType(K key) {
return dispatch(jsonCommandBuilder.jsonType(key, JsonPath.ROOT_PATH));
}

@Override
public RedisFuture<List<K>> keys(K pattern) {
return dispatch(commandBuilder.keys(pattern));
Expand Down Expand Up @@ -3194,6 +3378,11 @@ public RedisFuture<List<Map<String, Object>>> clusterLinks() {
return dispatch(commandBuilder.clusterLinks());
}

@Override
public JsonParser getJsonParser() {
return this.parser;
}

private byte[] encodeFunction(String functionCode) {
LettuceAssert.notNull(functionCode, "Function code must not be null");
LettuceAssert.notEmpty(functionCode, "Function code script must not be empty");
Expand Down
Loading

0 comments on commit 547271c

Please sign in to comment.