From 5a80ab617320af3139fd93234c0c6debc3cbb2c3 Mon Sep 17 00:00:00 2001 From: dengliming Date: Thu, 28 Jul 2022 21:30:35 +0800 Subject: [PATCH] Add support for redisgraph commands. --- README.md | 2 +- all/pom.xml | 5 + pom.xml | 7 + redisgraph/README.md | 16 ++ redisgraph/pom.xml | 23 ++ .../redismodule/redisgraph/RedisGraph.java | 215 +++++++++++++++ .../redisgraph/RedisGraphBatch.java | 32 +++ .../redisgraph/client/RedisGraphClient.java | 49 ++++ .../redisgraph/enums/ColumnType.java | 11 + .../redisgraph/enums/ScalarType.java | 28 ++ .../redismodule/redisgraph/model/Edge.java | 31 +++ .../redisgraph/model/GraphEntity.java | 25 ++ .../redismodule/redisgraph/model/Header.java | 29 ++ .../redismodule/redisgraph/model/Node.java | 4 + .../redismodule/redisgraph/model/Path.java | 21 ++ .../redismodule/redisgraph/model/Point.java | 36 +++ .../redisgraph/model/Property.java | 26 ++ .../redismodule/redisgraph/model/Record.java | 45 +++ .../redisgraph/model/ResultSet.java | 28 ++ .../redisgraph/model/SlowLogItem.java | 48 ++++ .../redisgraph/model/Statistics.java | 94 +++++++ .../redisgraph/protocol/Keywords.java | 38 +++ .../redisgraph/protocol/RedisCommands.java | 58 ++++ .../protocol/decoder/ResultSetDecoder.java | 258 ++++++++++++++++++ .../protocol/decoder/SlowLogItemDecoder.java | 35 +++ .../redismodule/redisgraph/AbstractTest.java | 54 ++++ .../redisgraph/RedisGraphTest.java | 140 ++++++++++ 27 files changed, 1357 insertions(+), 1 deletion(-) create mode 100644 redisgraph/README.md create mode 100644 redisgraph/pom.xml create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraph.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraphBatch.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/client/RedisGraphClient.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ColumnType.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ScalarType.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Edge.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/GraphEntity.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Header.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Node.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Path.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Point.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Property.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Record.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/ResultSet.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/SlowLogItem.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Statistics.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/Keywords.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/RedisCommands.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/ResultSetDecoder.java create mode 100644 redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/SlowLogItemDecoder.java create mode 100644 redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/AbstractTest.java create mode 100644 redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/RedisGraphTest.java diff --git a/README.md b/README.md index 3cccb4c..2934e6b 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Java Client libraries for [redis-modules](https://redis.io/modules), based on [R * [RedisAI](redisai) * [RedisGears](redisgears) * [RedisJSON](redisjson) +* [RedisGraph](redisgraph) ## TODO -* [RedisGraph](https://oss.redislabs.com/redisgraph/) * [RediSQL](https://redisql.com/) * [...](https://redis.io/modules) diff --git a/all/pom.xml b/all/pom.xml index 16edac9..f125ed3 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -43,5 +43,10 @@ ${project.groupId} redisjson + + + ${project.groupId} + redisgraph + diff --git a/pom.xml b/pom.xml index 2257b8a..2afd87c 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ redistimeseries redisgears redisjson + redisgraph commons @@ -111,6 +112,12 @@ redisjson ${project.version} + + + io.github.dengliming.redismodule + redisgraph + ${project.version} + diff --git a/redisgraph/README.md b/redisgraph/README.md new file mode 100644 index 0000000..c5bdff1 --- /dev/null +++ b/redisgraph/README.md @@ -0,0 +1,16 @@ +# Java Client for RedisGraph +See https://oss.redislabs.com/redisgraph/ for more details. + +## Redis commands mapping +Redis command|Sync / Async Api| +| --- | --- | +GRAPH.CONFIG | RedisGraph.
getConfig()
getConfigAsync()
setConfig()
setConfigAsync() | +GRAPH.DELETE | RedisGraph.
delete()
deleteAsync()
| +GRAPH.EXPLAIN | RedisGraph.
explain()
explainAsync()
| +GRAPH.LIST | RedisGraph.
list()
listAsync()
| +GRAPH.PROFILE | RedisGraph.
profile()
profileAsync()
| +GRAPH.QUERY | RedisGraph.
query()
queryAsync()
| +GRAPH.RO_QUERY | RedisGraph.
readOnlyQuery()
readOnlyQueryAsync()
| +GRAPH.SLOWLOG | RedisGraph.
slowLog()
slowLogAsync()
| + + diff --git a/redisgraph/pom.xml b/redisgraph/pom.xml new file mode 100644 index 0000000..12e8052 --- /dev/null +++ b/redisgraph/pom.xml @@ -0,0 +1,23 @@ + + + + redis-modules-java + io.github.dengliming.redismodule + 2.0.1-SNAPSHOT + + 4.0.0 + + redisgraph + RedisGraph + + + + + + io.github.dengliming.redismodule + commons + + + diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraph.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraph.java new file mode 100644 index 0000000..c8139e0 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraph.java @@ -0,0 +1,215 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph; + +import io.github.dengliming.redismodule.common.util.RAssert; +import io.github.dengliming.redismodule.redisgraph.model.ResultSet; +import io.github.dengliming.redismodule.redisgraph.model.SlowLogItem; +import org.redisson.api.RFuture; +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.StringCodec; +import org.redisson.command.CommandAsyncExecutor; + +import java.util.List; +import java.util.Map; + +import static io.github.dengliming.redismodule.redisgraph.protocol.Keywords.__COMPACT; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_CONFIG_GET; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_CONFIG_SET; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_DELETE; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_EXPLAIN; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_LIST; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_PROFILE; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_QUERY; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_READ_ONLY_QUERY; +import static io.github.dengliming.redismodule.redisgraph.protocol.RedisCommands.GRAPH_SLOWLOG; + +public class RedisGraph { + + private final CommandAsyncExecutor commandExecutor; + private final Codec codec; + + public RedisGraph(CommandAsyncExecutor commandExecutor) { + this(commandExecutor, commandExecutor.getConnectionManager().getCodec()); + } + + public RedisGraph(CommandAsyncExecutor commandExecutor, Codec codec) { + this.commandExecutor = commandExecutor; + this.codec = codec; + } + + /** + * Retrieves the current value of a RedisGraph configuration parameter. + *

+ * GRAPH.CONFIG GET name + * + * @param parameter + * @return + */ + public Map getConfig(String parameter) { + return commandExecutor.get(getConfigAsync(parameter)); + } + + public RFuture> getConfigAsync(String parameter) { + RAssert.notEmpty(parameter, "parameter must not be empty"); + + return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, GRAPH_CONFIG_GET, parameter); + } + + /** + * Set the value of a RedisGraph configuration parameter. + *

+ * GRAPH.CONFIG SET name value + * + * @param name + * @param value + * @return + */ + public Boolean setConfig(String name, Object value) { + return commandExecutor.get(setConfigAsync(name, value)); + } + + public RFuture setConfigAsync(String name, Object value) { + RAssert.notEmpty(name, "name must not be empty"); + RAssert.notNull(value, "value must not be null"); + + return commandExecutor.readAsync(getName(), codec, GRAPH_CONFIG_SET, name, value); + } + + /** + * Completely removes the graph and all of its entities. + *

+ * GRAPH.DELETE graph + * + * @param name + * @return + */ + public String delete(String name) { + return commandExecutor.get(deleteAsync(name)); + } + + public RFuture deleteAsync(String name) { + RAssert.notEmpty(name, "name must not be empty"); + + return commandExecutor.writeAsync(name, StringCodec.INSTANCE, GRAPH_DELETE, name); + } + + /** + * Lists all graph keys in the keyspace. + *

+ * GRAPH.LIST + * + * @return + */ + public List list() { + return commandExecutor.get(listAsync()); + } + + public RFuture> listAsync() { + return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, GRAPH_LIST); + } + + /** + * Executes a query and produces an execution plan augmented with metrics for each operation's execution. + *

+ * GRAPH.PROFILE graph query [TIMEOUT timeout] + * + * @return + */ + public List profile(String graphName, String query, long timeout) { + return commandExecutor.get(profileAsync(graphName, query, timeout)); + } + + public RFuture> profileAsync(String graphName, String query, long timeout) { + if (timeout > 0) { + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_PROFILE, graphName, query, timeout); + } + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_PROFILE, graphName, query); + } + + + /** + * Constructs a query execution plan but does not run it. Inspect this execution plan to better understand how + * your query will get executed. + *

+ * GRAPH.EXPLAIN graph query + * + * @return + */ + public List explain(String graphName, String query) { + return commandExecutor.get(explainAsync(graphName, query)); + } + + public RFuture> explainAsync(String graphName, String query) { + return commandExecutor.readAsync(graphName, codec, GRAPH_EXPLAIN, graphName, query); + } + + /** + * Returns a list containing up to 10 of the slowest queries issued against the given graph ID. + *

+ * GRAPH.SLOWLOG graph + * + * @return + */ + public List slowLog(String graphName) { + return commandExecutor.get(slowLogAsync(graphName)); + } + + public RFuture> slowLogAsync(String graphName) { + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_SLOWLOG, graphName); + } + + /** + * Executes the given query against a specified graph. + *

+ * GRAPH.QUERY graph query [TIMEOUT timeout] + * + * @return + */ + public ResultSet query(String graphName, String query, long timeout) { + return commandExecutor.get(queryAsync(graphName, query, timeout)); + } + + public RFuture queryAsync(String graphName, String query, long timeout) { + if (timeout > 0) { + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_QUERY, graphName, query, timeout, __COMPACT.getAlias()); + } + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_QUERY, graphName, query, __COMPACT.getAlias()); + } + + /** + * Executes a given read only query against a specified graph. + *

+ * GRAPH.RO_QUERY graph query [TIMEOUT timeout] + * + * @return + */ + public ResultSet readOnlyQuery(String graphName, String query, long timeout) { + return commandExecutor.get(readOnlyQueryAsync(graphName, query, timeout)); + } + + public RFuture readOnlyQueryAsync(String graphName, String query, long timeout) { + if (timeout > 0) { + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_READ_ONLY_QUERY, graphName, query, timeout, __COMPACT.getAlias()); + } + return commandExecutor.readAsync(graphName, StringCodec.INSTANCE, GRAPH_READ_ONLY_QUERY, graphName, query, __COMPACT.getAlias()); + } + + public String getName() { + return ""; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraphBatch.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraphBatch.java new file mode 100644 index 0000000..f8123b6 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/RedisGraphBatch.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph; + +import io.github.dengliming.redismodule.common.api.RCommonBatch; +import org.redisson.api.BatchOptions; +import org.redisson.command.CommandAsyncExecutor; + +public class RedisGraphBatch extends RCommonBatch { + + public RedisGraphBatch(CommandAsyncExecutor executor, BatchOptions options) { + super(executor, options); + } + + public RedisGraph getRedisGraph() { + return new RedisGraph(getExecutorService()); + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/client/RedisGraphClient.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/client/RedisGraphClient.java new file mode 100644 index 0000000..6b4ef0b --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/client/RedisGraphClient.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.client; + +import io.github.dengliming.redismodule.redisgraph.RedisGraph; +import io.github.dengliming.redismodule.redisgraph.RedisGraphBatch; +import org.redisson.Redisson; +import org.redisson.api.BatchOptions; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.config.Config; + +public class RedisGraphClient extends Redisson { + + public RedisGraphClient(Config config) { + super(config); + } + + public RedisGraph getRedisGraph() { + return new RedisGraph(getCommandExecutor()); + } + + public RedisGraphBatch createRedisGraphBatch() { + return this.createRedisGraphBatch(BatchOptions.defaults()); + } + + public RedisGraphBatch createRedisGraphBatch(BatchOptions options) { + return new RedisGraphBatch(getCommandExecutor(), options); + } + + public Void flushall() { + CommandAsyncExecutor commandExecutor = getCommandExecutor(); + return commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHALL)); + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ColumnType.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ColumnType.java new file mode 100644 index 0000000..bb997b5 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ColumnType.java @@ -0,0 +1,11 @@ +package io.github.dengliming.redismodule.redisgraph.enums; + +public enum ColumnType { + + UNKNOWN, + SCALAR, + NODE, + RELATION; + + public static final ColumnType[] COLUMN_TYPES = ColumnType.values(); +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ScalarType.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ScalarType.java new file mode 100644 index 0000000..ee73218 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/enums/ScalarType.java @@ -0,0 +1,28 @@ +package io.github.dengliming.redismodule.redisgraph.enums; + +import org.redisson.client.RedisException; + +public enum ScalarType { + UNKNOWN, + NULL, + STRING, + INTEGER, + BOOLEAN, + DOUBLE, + ARRAY, + EDGE, + NODE, + PATH, + MAP, + POINT; + + public static final ScalarType[] SCALAR_TYPES = ScalarType.values(); + + public static ScalarType getScalarType(int index) { + try { + return SCALAR_TYPES[index]; + } catch (IndexOutOfBoundsException e) { + throw new RedisException("Unrecognized response type"); + } + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Edge.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Edge.java new file mode 100644 index 0000000..cc113e7 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Edge.java @@ -0,0 +1,31 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +public class Edge extends GraphEntity { + private int relationshipTypeIndex; + private long source; + private long destination; + + public int getRelationshipTypeIndex() { + return relationshipTypeIndex; + } + + public void setRelationshipTypeIndex(int relationshipTypeIndex) { + this.relationshipTypeIndex = relationshipTypeIndex; + } + + public long getSource() { + return source; + } + + public void setSource(long source) { + this.source = source; + } + + public long getDestination() { + return destination; + } + + public void setDestination(long destination) { + this.destination = destination; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/GraphEntity.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/GraphEntity.java new file mode 100644 index 0000000..1c2d9c9 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/GraphEntity.java @@ -0,0 +1,25 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import java.util.ArrayList; +import java.util.List; + +public abstract class GraphEntity { + private long id; + private final List> propertyList = new ArrayList<>(); + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public void addProperty(int index, String name, Object value) { + propertyList.add(new Property(index, name, value)); + } + + public List> getPropertyList() { + return propertyList; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Header.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Header.java new file mode 100644 index 0000000..be1e227 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Header.java @@ -0,0 +1,29 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import io.github.dengliming.redismodule.redisgraph.enums.ColumnType; + +import java.util.Collections; +import java.util.List; + +public class Header { + private final List schemaTypes; + private final List schemaNames; + + public Header() { + this.schemaTypes = Collections.emptyList(); + this.schemaNames = Collections.emptyList(); + } + + public Header(List schemaTypes, List schemaNames) { + this.schemaTypes = schemaTypes; + this.schemaNames = schemaNames; + } + + public List getSchemaTypes() { + return schemaTypes; + } + + public List getSchemaNames() { + return schemaNames; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Node.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Node.java new file mode 100644 index 0000000..384fa08 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Node.java @@ -0,0 +1,4 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +public class Node extends GraphEntity { +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Path.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Path.java new file mode 100644 index 0000000..8ffd954 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Path.java @@ -0,0 +1,21 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import java.util.List; + +public class Path { + private final List nodes; + private final List edges; + + public Path(List nodes, List edges) { + this.nodes = nodes; + this.edges = edges; + } + + public List getNodes() { + return nodes; + } + + public List getEdges() { + return edges; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Point.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Point.java new file mode 100644 index 0000000..a4c8c08 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Point.java @@ -0,0 +1,36 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import java.util.List; + +public class Point { + + private static final double EPSILON = 1e-5; + + private final double latitude; + private final double longitude; + + /** + * @param latitude + * @param longitude + */ + public Point(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + public Point(List values) { + if (values == null || values.size() != 2) { + throw new IllegalArgumentException("Point requires two doubles."); + } + this.latitude = values.get(0); + this.longitude = values.get(1); + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Property.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Property.java new file mode 100644 index 0000000..c4d3398 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Property.java @@ -0,0 +1,26 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +public class Property { + + private final int index; + private final String name; + private final T value; + + public Property(int index, String name, T value) { + this.index = index; + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public T getValue() { + return value; + } + + public int getIndex() { + return index; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Record.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Record.java new file mode 100644 index 0000000..5a9cc08 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Record.java @@ -0,0 +1,45 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import java.util.List; + +public class Record { + private final List header; + private final List values; + + public Record(List header, List values) { + this.header = header; + this.values = values; + } + + public T getValue(int index) { + return (T) this.values.get(index); + } + + public T getValue(String key) { + return getValue(this.header.indexOf(key)); + } + + public String getString(int index) { + return this.values.get(index).toString(); + } + + public String getString(String key) { + return getString(this.header.indexOf(key)); + } + + public List keys() { + return header; + } + + public List values() { + return this.values; + } + + public boolean containsKey(String key) { + return this.header.contains(key); + } + + public int size() { + return this.header.size(); + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/ResultSet.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/ResultSet.java new file mode 100644 index 0000000..6053794 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/ResultSet.java @@ -0,0 +1,28 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +import java.util.List; + +public class ResultSet { + + private final Header header; + private final List results; + private final Statistics statistics; + + public ResultSet(Header header, List results, Statistics statistics) { + this.header = header; + this.results = results; + this.statistics = statistics; + } + + public Header getHeader() { + return header; + } + + public List getResults() { + return results; + } + + public Statistics getStatistics() { + return statistics; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/SlowLogItem.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/SlowLogItem.java new file mode 100644 index 0000000..b3ef32a --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/SlowLogItem.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.model; + +public class SlowLogItem { + + private final String timestamp; + private final String command; + private final String query; + private final String amountOfTime; + + public SlowLogItem(String timestamp, String command, String query, String amountOfTime) { + this.timestamp = timestamp; + this.command = command; + this.query = query; + this.amountOfTime = amountOfTime; + } + + public String getTimestamp() { + return timestamp; + } + + public String getCommand() { + return command; + } + + public String getQuery() { + return query; + } + + public String getAmountOfTime() { + return amountOfTime; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Statistics.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Statistics.java new file mode 100644 index 0000000..ffc15c3 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/model/Statistics.java @@ -0,0 +1,94 @@ +package io.github.dengliming.redismodule.redisgraph.model; + +public class Statistics { + private int nodesCreated; + private int nodesDeleted; + private int indicesCreated; + private int indicesDeleted; + private int labelsAdded; + private int propertiesSet; + private int relationshipsCreated; + private int relationshipsDeleted; + private boolean cachedExecution; + private String queryIntervalExecutionTime; + + public int getNodesCreated() { + return nodesCreated; + } + + public int getNodesDeleted() { + return nodesDeleted; + } + + public int getIndicesCreated() { + return indicesCreated; + } + + public int getIndicesDeleted() { + return indicesDeleted; + } + + public int getLabelsAdded() { + return labelsAdded; + } + + public int getPropertiesSet() { + return propertiesSet; + } + + public int getRelationshipsCreated() { + return relationshipsCreated; + } + + public int getRelationshipsDeleted() { + return relationshipsDeleted; + } + + public boolean isCachedExecution() { + return cachedExecution; + } + + public String getQueryIntervalExecutionTime() { + return queryIntervalExecutionTime; + } + + public void setNodesCreated(int nodesCreated) { + this.nodesCreated = nodesCreated; + } + + public void setNodesDeleted(int nodesDeleted) { + this.nodesDeleted = nodesDeleted; + } + + public void setIndicesCreated(int indicesCreated) { + this.indicesCreated = indicesCreated; + } + + public void setIndicesDeleted(int indicesDeleted) { + this.indicesDeleted = indicesDeleted; + } + + public void setLabelsAdded(int labelsAdded) { + this.labelsAdded = labelsAdded; + } + + public void setPropertiesSet(int propertiesSet) { + this.propertiesSet = propertiesSet; + } + + public void setRelationshipsCreated(int relationshipsCreated) { + this.relationshipsCreated = relationshipsCreated; + } + + public void setRelationshipsDeleted(int relationshipsDeleted) { + this.relationshipsDeleted = relationshipsDeleted; + } + + public void setCachedExecution(boolean cachedExecution) { + this.cachedExecution = cachedExecution; + } + + public void setQueryIntervalExecutionTime(String queryIntervalExecutionTime) { + this.queryIntervalExecutionTime = queryIntervalExecutionTime; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/Keywords.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/Keywords.java new file mode 100644 index 0000000..0a491c8 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/Keywords.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.protocol; + +/** + * @author dengliming + */ +public enum Keywords { + __COMPACT("--COMPACT"); + + private String alias; + + Keywords() { + this.alias = name(); + } + + Keywords(String alias) { + this.alias = alias; + } + + public String getAlias() { + return alias; + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/RedisCommands.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/RedisCommands.java new file mode 100644 index 0000000..371d669 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/RedisCommands.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.protocol; + +import io.github.dengliming.redismodule.redisgraph.protocol.decoder.SlowLogItemDecoder; +import io.github.dengliming.redismodule.redisgraph.protocol.decoder.ResultSetDecoder; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.decoder.ListMultiDecoder2; +import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; +import org.redisson.client.protocol.decoder.ObjectMapReplayDecoder; + +/** + * @author dengliming + */ +public interface RedisCommands { + RedisCommand GRAPH_CONFIG_SET = new RedisCommand<>("GRAPH.CONFIG", "SET", new BooleanReplayConvertor()); + RedisCommand GRAPH_CONFIG_GET = new RedisCommand<>("GRAPH.CONFIG", "GET", new ObjectMapReplayDecoder()); + + RedisCommand GRAPH_DELETE = new RedisCommand<>("GRAPH.DELETE"); + RedisCommand GRAPH_LIST = new RedisCommand<>("GRAPH.LIST", new ObjectListReplayDecoder()); + RedisCommand GRAPH_PROFILE = new RedisCommand<>("GRAPH.PROFILE", new ObjectListReplayDecoder()); + RedisCommand GRAPH_EXPLAIN = new RedisCommand<>("GRAPH.EXPLAIN", new ObjectListReplayDecoder()); + RedisCommand GRAPH_SLOWLOG = new RedisCommand<>("GRAPH.SLOWLOG", new ListMultiDecoder2(new ObjectListReplayDecoder<>(), new SlowLogItemDecoder())); + RedisCommand GRAPH_QUERY = new RedisCommand<>("GRAPH.QUERY", new ListMultiDecoder2( + new ResultSetDecoder(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>() + )); + + RedisCommand GRAPH_READ_ONLY_QUERY = new RedisCommand<>("GRAPH.RO_QUERY", new ListMultiDecoder2( + new ResultSetDecoder(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>(), + new ObjectListReplayDecoder<>() + )); +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/ResultSetDecoder.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/ResultSetDecoder.java new file mode 100644 index 0000000..a6b572b --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/ResultSetDecoder.java @@ -0,0 +1,258 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.protocol.decoder; + +import io.github.dengliming.redismodule.redisgraph.enums.ColumnType; +import io.github.dengliming.redismodule.redisgraph.enums.ScalarType; +import io.github.dengliming.redismodule.redisgraph.model.Edge; +import io.github.dengliming.redismodule.redisgraph.model.GraphEntity; +import io.github.dengliming.redismodule.redisgraph.model.Header; +import io.github.dengliming.redismodule.redisgraph.model.Node; +import io.github.dengliming.redismodule.redisgraph.model.Path; +import io.github.dengliming.redismodule.redisgraph.model.Point; +import io.github.dengliming.redismodule.redisgraph.model.Record; +import io.github.dengliming.redismodule.redisgraph.model.ResultSet; +import io.github.dengliming.redismodule.redisgraph.model.Statistics; +import org.redisson.client.RedisException; +import org.redisson.client.handler.State; +import org.redisson.client.protocol.decoder.MultiDecoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.github.dengliming.redismodule.redisgraph.enums.ColumnType.COLUMN_TYPES; +import static io.github.dengliming.redismodule.redisgraph.enums.ScalarType.getScalarType; + +public class ResultSetDecoder implements MultiDecoder { + + @Override + public ResultSet decode(List parts, State state) { + Statistics statistics; + Header header = null; + List records = null; + if (parts.size() == 1) { + statistics = parseStatistics(parts.get(0)); + } else if (parts.size() == 3) { + header = parseHeader(parts.get(0)); + records = parseRecords(header, parts.get(1)); + statistics = parseStatistics(parts.get(2)); + } else { + throw new RedisException("Unrecognized graph response format."); + } + + return new ResultSet(header, records, statistics); + } + + private Statistics parseStatistics(Object data) { + Statistics statistics = new Statistics(); + ((List) data).stream() + .map(s -> s.split(": ")) + .forEach(kv -> { + switch (kv[0]) { + case "Nodes created": + statistics.setNodesCreated(getIntValue(kv[1])); + break; + case "Nodes deleted": + statistics.setNodesDeleted(getIntValue(kv[1])); + break; + case "Indices created": + statistics.setIndicesCreated(getIntValue(kv[1])); + break; + case "Indices deleted": + statistics.setIndicesDeleted(getIntValue(kv[1])); + break; + case "Labels added": + statistics.setLabelsAdded(getIntValue(kv[1])); + break; + case "Relationships deleted": + statistics.setRelationshipsDeleted(getIntValue(kv[1])); + break; + case "Relationships created": + statistics.setRelationshipsCreated(getIntValue(kv[1])); + break; + case "Properties set": + statistics.setPropertiesSet(getIntValue(kv[1])); + break; + case "Cached execution": + statistics.setCachedExecution("1".equals(kv[1])); + break; + case "Query internal execution time": + statistics.setQueryIntervalExecutionTime(kv[1]); + break; + default: + break; + } + }); + return statistics; + } + + private List parseRecords(Header header, Object data) { + List> rawResultSet = (List>) data; + List results = new ArrayList<>(); + if (rawResultSet == null || rawResultSet.isEmpty()) { + return results; + } + + // go over each raw result + for (List row : rawResultSet) { + + List parsedRow = new ArrayList<>(row.size()); + // go over each object in the result + for (int i = 0; i < row.size(); i++) { + // get raw representation of the object + List obj = (List) row.get(i); + // get object type + ColumnType objType = header.getSchemaTypes().get(i); + // deserialize according to type and + switch (objType) { + case NODE: + parsedRow.add(deserializeNode(obj)); + break; + case RELATION: + parsedRow.add(deserializeEdge(obj)); + break; + case SCALAR: + parsedRow.add(deserializeScalar(obj)); + break; + default: + parsedRow.add(null); + break; + } + + } + // create new record from deserialized objects + Record record = new Record(header.getSchemaNames(), parsedRow); + results.add(record); + } + return results; + } + + private Node deserializeNode(List rawNodeData) { + Node node = new Node(); + node.setId((Long) rawNodeData.get(0)); + deserializeGraphEntityProperties(node, (List>) rawNodeData.get(2)); + return node; + } + + private void deserializeGraphEntityProperties(GraphEntity entity, List> rawProperties) { + for (List rawProperty : rawProperties) { + // trimmed for getting to value using deserializeScalar + List propertyScalar = rawProperty.subList(1, rawProperty.size()); + // TODO + String name = ""; + entity.addProperty(((Long) rawProperty.get(0)).intValue(), name, deserializeScalar(propertyScalar)); + } + } + + private Object deserializeScalar(List rawScalarData) { + ScalarType type = getValueTypeFromObject(rawScalarData.get(0)); + + Object obj = rawScalarData.get(1); + switch (type) { + case NULL: + return null; + case BOOLEAN: + return Boolean.parseBoolean((String) obj); + case ARRAY: + return deserializeArray(obj); + case NODE: + return deserializeNode((List) obj); + case EDGE: + return deserializeEdge((List) obj); + case PATH: + return deserializePath(obj); + case MAP: + return deserializeMap(obj); + case POINT: + return deserializePoint(obj); + case UNKNOWN: + default: + return obj; + } + } + + private Object deserializePoint(Object rawScalarData) { + return new Point((List) rawScalarData); + } + + private Edge deserializeEdge(List rawEdgeData) { + Edge edge = new Edge(); + edge.setId((Long) rawEdgeData.get(0)); + edge.setRelationshipTypeIndex(((Long) rawEdgeData.get(1)).intValue()); + + edge.setSource((long) rawEdgeData.get(2)); + edge.setDestination((long) rawEdgeData.get(3)); + deserializeGraphEntityProperties(edge, (List>) rawEdgeData.get(4)); + return edge; + } + + @SuppressWarnings("unchecked") + private Map deserializeMap(Object rawScalarData) { + List keyTypeValueEntries = (List) rawScalarData; + Map map = new HashMap<>(); + for (int i = 0; i < keyTypeValueEntries.size(); i += 2) { + String key = (String) keyTypeValueEntries.get(i); + Object value = deserializeScalar((List) keyTypeValueEntries.get(i + 1)); + map.put(key, value); + } + return map; + } + + @SuppressWarnings("unchecked") + private Path deserializePath(Object rawScalarData) { + List> array = (List>) rawScalarData; + List nodes = (List) deserializeScalar(array.get(0)); + List edges = (List) deserializeScalar(array.get(1)); + return new Path(nodes, edges); + } + + @SuppressWarnings("unchecked") + private List deserializeArray(Object rawScalarData) { + List> array = (List>) rawScalarData; + List res = new ArrayList<>(array.size()); + for (List arrayValue : array) { + res.add(deserializeScalar(arrayValue)); + } + return res; + } + + private ScalarType getValueTypeFromObject(Object rawScalarType) { + return getScalarType(((Long) rawScalarType).intValue()); + } + + private Header parseHeader(Object data) { + if (data == null) { + return new Header(); + } + + List> list = (List>) data; + List types = new ArrayList<>(list.size()); + List texts = new ArrayList<>(list.size()); + for (List tuple : list) { + types.add(COLUMN_TYPES[((Long) tuple.get(0)).intValue()]); + texts.add((String) tuple.get(1)); + } + return new Header(types, texts); + } + + private int getIntValue(String v) { + return Optional.ofNullable(v).map(Integer::parseInt).orElse(0); + } +} diff --git a/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/SlowLogItemDecoder.java b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/SlowLogItemDecoder.java new file mode 100644 index 0000000..56122c6 --- /dev/null +++ b/redisgraph/src/main/java/io/github/dengliming/redismodule/redisgraph/protocol/decoder/SlowLogItemDecoder.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph.protocol.decoder; + +import io.github.dengliming.redismodule.redisgraph.model.SlowLogItem; +import org.redisson.client.handler.State; +import org.redisson.client.protocol.decoder.MultiDecoder; + +import java.util.List; + +public class SlowLogItemDecoder implements MultiDecoder { + + @Override + public SlowLogItem decode(List parts, State state) { + if (parts.size() < 4) { + return null; + } + return new SlowLogItem((String) parts.get(0), (String) parts.get(1), (String) parts.get(2), + (String) parts.get(3)); + } +} diff --git a/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/AbstractTest.java b/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/AbstractTest.java new file mode 100644 index 0000000..87a7663 --- /dev/null +++ b/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/AbstractTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph; + +import io.github.dengliming.redismodule.common.util.TestSettings; +import io.github.dengliming.redismodule.redisgraph.client.RedisGraphClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.redisson.config.Config; + +/** + * @author dengliming + */ +public abstract class AbstractTest { + + private RedisGraphClient redisGraphClient; + + @BeforeEach + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://" + TestSettings.host() + ":" + TestSettings.port()); + redisGraphClient = new RedisGraphClient(config); + redisGraphClient.flushall(); + } + + @AfterEach + public void destroy() { + if (redisGraphClient != null) { + redisGraphClient.shutdown(); + } + } + + public RedisGraph getRedisGraph() { + return redisGraphClient == null ? null : redisGraphClient.getRedisGraph(); + } + + public RedisGraphBatch getRedisGraphBatch() { + return redisGraphClient == null ? null : redisGraphClient.createRedisGraphBatch(); + } +} diff --git a/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/RedisGraphTest.java b/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/RedisGraphTest.java new file mode 100644 index 0000000..a287dad --- /dev/null +++ b/redisgraph/src/test/java/io/github/dengliming/redismodule/redisgraph/RedisGraphTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2022 dengliming. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.dengliming.redismodule.redisgraph; + +import io.github.dengliming.redismodule.redisgraph.model.Header; +import io.github.dengliming.redismodule.redisgraph.model.Node; +import io.github.dengliming.redismodule.redisgraph.model.Record; +import io.github.dengliming.redismodule.redisgraph.model.ResultSet; +import io.github.dengliming.redismodule.redisgraph.model.SlowLogItem; +import io.github.dengliming.redismodule.redisgraph.model.Statistics; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author dengliming + */ +public class RedisGraphTest extends AbstractTest { + + @Test + public void testConfig() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.setConfig("TIMEOUT", 10000)).isTrue(); + + Map configMap = redisGraph.getConfig("TIMEOUT"); + assertThat(configMap).containsEntry("TIMEOUT", 10000L); + } + + @Test + public void testDelete() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.query("social", + "CREATE (:person{name:'roi',age:32, doubleValue:3.14, boolValue:true})", -1)).isNotNull(); + assertThat(redisGraph.delete("social")).contains("Graph removed"); + } + + @Test + public void testList() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.profile("social", "CREATE (:person{name:'roi',age:32})", 0L)).isNotEmpty(); + List graphList = redisGraph.list(); + assertThat(graphList).contains("social"); + } + + @Test + public void testSlowLog() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.profile("social", "CREATE (:person{name:'roi',age:32})", 0L)).isNotEmpty(); + assertThat(redisGraph.profile("social", "CREATE (:person{name:'amit',age:30})", 0L)).isNotEmpty(); + + List slowLogItems = redisGraph.slowLog("social"); + assertThat(slowLogItems).isNotNull(); + assertThat(slowLogItems.size()).isEqualTo(2); + assertThat(slowLogItems.get(0).getCommand()).isEqualToIgnoringCase("GRAPH.PROFILE"); + } + + @Test + public void testQuery() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.query("social", "CREATE (:person{name:'filipe',age:30})", 0L)).isNotNull(); + assertThat(redisGraph.query("social", "MATCH (a:person) WHERE (a.name = 'filipe') RETURN a.age", 0L)).isNotNull(); + } + + @Test + public void testHeader() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.query("social", "CREATE (:person{name:'roi',age:32})", 0L)).isNotNull(); + assertThat(redisGraph.query("social", "CREATE (:person{name:'amit',age:30})", 0L)).isNotNull(); + assertThat(redisGraph.query("social", + "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(a)", + 0L)).isNotNull(); + + ResultSet resultSet = redisGraph.query("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r, a.age", 0L); + assertThat(resultSet).isNotNull(); + Header header = resultSet.getHeader(); + assertThat(header).isNotNull(); + List schemaNames = header.getSchemaNames(); + + assertThat(schemaNames).isNotNull(); + assertThat(schemaNames.size()).isEqualTo(3); + assertThat(schemaNames.get(0)).isEqualTo("a"); + assertThat(schemaNames.get(1)).isEqualTo("r"); + assertThat(schemaNames.get(2)).isEqualTo("a.age"); + } + + @Test + public void testRecord() { + RedisGraph redisGraph = getRedisGraph(); + assertThat(redisGraph.query("social", + "CREATE (:person{name:'roi',age:32, doubleValue:3.14, boolValue:true})", -1)).isNotNull(); + assertThat(redisGraph.query("social", "CREATE (:person{name:'amit',age:30})", -1)).isNotNull(); + assertThat(redisGraph.query("social", "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') " + + "CREATE (a)-[:knows{place:'TLV', since:2000,doubleValue:3.14, boolValue:false}]->(b)", -1)).isNotNull(); + + ResultSet resultSet = redisGraph.query("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r, " + + "a.name, a.age, a.doubleValue, a.boolValue, " + "r.place, r.since, r.doubleValue, r.boolValue", -1); + assertThat(resultSet).isNotNull(); + Statistics stats = resultSet.getStatistics(); + assertThat(stats.getNodesCreated()).isEqualTo(0); + assertThat(stats.getNodesDeleted()).isEqualTo(0); + assertThat(stats.getLabelsAdded()).isEqualTo(0); + assertThat(stats.getPropertiesSet()).isEqualTo(0); + assertThat(stats.getRelationshipsCreated()).isEqualTo(0); + assertThat(stats.getRelationshipsDeleted()).isEqualTo(0); + + assertThat(resultSet.getResults().size()).isEqualTo(1); + Record record = resultSet.getResults().get(0); + Node node = (Node) record.getValue(0); + assertThat(node).isNotNull(); + assertThat(node.getPropertyList().get(0).getValue()).isEqualTo("roi"); + + assertThat(record.getString(2)).isEqualTo("roi"); + assertThat(record.getString(3)).isEqualTo("32"); + assertThat(((Long) record.getValue(3)).longValue()).isEqualTo(32L); + assertThat(((Long) record.getValue("a.age")).longValue()).isEqualTo(32L); + assertThat(record.getString("a.name")).isEqualTo("roi"); + assertThat(record.getString("a.age")).isEqualTo("32"); + + resultSet = redisGraph.readOnlyQuery("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r, " + + "a.name, a.age, a.doubleValue, a.boolValue, " + "r.place, r.since, r.doubleValue, r.boolValue", -1); + assertThat(resultSet).isNotNull(); + } +}