diff --git a/src/main/java/redis/clients/jedis/ClusterPipeline.java b/src/main/java/redis/clients/jedis/ClusterPipeline.java index b79ad84607..90194f0c18 100644 --- a/src/main/java/redis/clients/jedis/ClusterPipeline.java +++ b/src/main/java/redis/clients/jedis/ClusterPipeline.java @@ -44,4 +44,11 @@ protected HostAndPort getNodeKey(CommandArguments args) { protected Connection getConnection(HostAndPort nodeKey) { return provider.getConnection(nodeKey); } + + /** + * This method must be called after constructor, if graph commands are going to be used. + */ + public void prepareGraphCommands() { + super.prepareGraphCommands(provider); + } } diff --git a/src/main/java/redis/clients/jedis/MultiNodePipelineBase.java b/src/main/java/redis/clients/jedis/MultiNodePipelineBase.java index df2c7cff1a..12d8f9f862 100644 --- a/src/main/java/redis/clients/jedis/MultiNodePipelineBase.java +++ b/src/main/java/redis/clients/jedis/MultiNodePipelineBase.java @@ -17,10 +17,13 @@ import redis.clients.jedis.commands.PipelineBinaryCommands; import redis.clients.jedis.commands.PipelineCommands; import redis.clients.jedis.commands.RedisModulePipelineCommands; +import redis.clients.jedis.graph.GraphCommandObjects; +import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.json.JsonSetParams; import redis.clients.jedis.json.Path; import redis.clients.jedis.json.Path2; import redis.clients.jedis.params.*; +import redis.clients.jedis.providers.ConnectionProvider; import redis.clients.jedis.resps.*; import redis.clients.jedis.search.IndexOptions; import redis.clients.jedis.search.Query; @@ -39,6 +42,7 @@ public abstract class MultiNodePipelineBase implements PipelineCommands, Pipelin private volatile boolean synced; private final CommandObjects commandObjects; + private GraphCommandObjects graphCommandObjects; public MultiNodePipelineBase(CommandObjects commandObjects) { pipelinedResponses = new LinkedHashMap<>(); @@ -47,6 +51,13 @@ public MultiNodePipelineBase(CommandObjects commandObjects) { this.commandObjects = commandObjects; } + /** + * Sub-classes must call this method, if graph commands are going to be used. + */ + protected final void prepareGraphCommands(ConnectionProvider connectionProvider) { + this.graphCommandObjects = new GraphCommandObjects(connectionProvider); + } + protected abstract HostAndPort getNodeKey(CommandArguments args); protected abstract Connection getConnection(HostAndPort nodeKey); @@ -3952,6 +3963,53 @@ public Response> topkInfo(String key) { } // RedisBloom commands + // RedisGraph commands + @Override + public Response graphQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphQuery(name, query)); + } + + @Override + public Response graphReadonlyQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query)); + } + + @Override + public Response graphQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, timeout)); + } + + @Override + public Response graphQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params)); + } + + @Override + public Response graphQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params, timeout)); + } + + @Override + public Response graphDelete(String name) { + return appendCommand(graphCommandObjects.graphDelete(name)); + } + // RedisGraph commands + public Response waitReplicas(int replicas, long timeout) { return appendCommand(commandObjects.waitReplicas(replicas, timeout)); } diff --git a/src/main/java/redis/clients/jedis/Pipeline.java b/src/main/java/redis/clients/jedis/Pipeline.java index 5e84905383..21614525bc 100644 --- a/src/main/java/redis/clients/jedis/Pipeline.java +++ b/src/main/java/redis/clients/jedis/Pipeline.java @@ -18,6 +18,8 @@ import redis.clients.jedis.commands.ProtocolCommand; import redis.clients.jedis.commands.RedisModulePipelineCommands; import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.graph.GraphCommandObjects; +import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.json.JsonSetParams; import redis.clients.jedis.json.Path; import redis.clients.jedis.json.Path2; @@ -38,12 +40,14 @@ public class Pipeline extends Queable implements PipelineCommands, PipelineBinar protected final Connection connection; // private final Jedis jedis; private final CommandObjects commandObjects; + private final GraphCommandObjects graphCommandObjects; public Pipeline(Connection connection) { // super(connection); this.connection = connection; // this.jedis = null; this.commandObjects = new CommandObjects(); + this.graphCommandObjects = new GraphCommandObjects(this.connection); } public Pipeline(Jedis jedis) { @@ -51,6 +55,7 @@ public Pipeline(Jedis jedis) { this.connection = jedis.getConnection(); // this.jedis = jedis; this.commandObjects = new CommandObjects(); + this.graphCommandObjects = new GraphCommandObjects(this.connection); } public final Response appendCommand(CommandObject commandObject) { @@ -3960,6 +3965,53 @@ public Response> topkInfo(String key) { } // RedisBloom commands + // RedisGraph commands + @Override + public Response graphQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphQuery(name, query)); + } + + @Override + public Response graphReadonlyQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query)); + } + + @Override + public Response graphQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, timeout)); + } + + @Override + public Response graphQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params)); + } + + @Override + public Response graphQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params, timeout)); + } + + @Override + public Response graphDelete(String name) { + return appendCommand(graphCommandObjects.graphDelete(name)); + } + // RedisGraph commands + public Response waitReplicas(int replicas, long timeout) { return appendCommand(commandObjects.waitReplicas(replicas, timeout)); } diff --git a/src/main/java/redis/clients/jedis/ShardedPipeline.java b/src/main/java/redis/clients/jedis/ShardedPipeline.java index 15d10eee20..7548353497 100644 --- a/src/main/java/redis/clients/jedis/ShardedPipeline.java +++ b/src/main/java/redis/clients/jedis/ShardedPipeline.java @@ -51,4 +51,11 @@ protected HostAndPort getNodeKey(CommandArguments args) { protected Connection getConnection(HostAndPort nodeKey) { return provider.getConnection(nodeKey); } + + /** + * This method must be called after constructor, if graph commands are going to be used. + */ + public void prepareGraphCommands() { + super.prepareGraphCommands(provider); + } } diff --git a/src/main/java/redis/clients/jedis/TransactionBase.java b/src/main/java/redis/clients/jedis/TransactionBase.java index 50e611f2ef..c51ecb5856 100644 --- a/src/main/java/redis/clients/jedis/TransactionBase.java +++ b/src/main/java/redis/clients/jedis/TransactionBase.java @@ -24,6 +24,8 @@ import redis.clients.jedis.commands.RedisModulePipelineCommands; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.graph.GraphCommandObjects; +import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.json.JsonSetParams; import redis.clients.jedis.json.Path; import redis.clients.jedis.json.Path2; @@ -43,6 +45,7 @@ public abstract class TransactionBase extends Queable implements PipelineCommand protected final Connection connection; private final CommandObjects commandObjects; + private final GraphCommandObjects graphCommandObjects; private boolean broken = false; private boolean inWatch = false; @@ -58,6 +61,7 @@ public TransactionBase(Connection connection) { public TransactionBase(Connection connection, boolean doMulti) { this.connection = connection; this.commandObjects = new CommandObjects(); + this.graphCommandObjects = new GraphCommandObjects(this.connection); if (doMulti) multi(); } @@ -4029,6 +4033,53 @@ public Response> topkInfo(String key) { } // RedisBloom commands + // RedisGraph commands + @Override + public Response graphQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphQuery(name, query)); + } + + @Override + public Response graphReadonlyQuery(String name, String query) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query)); + } + + @Override + public Response graphQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, timeout)); + } + + @Override + public Response graphQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params)); + } + + @Override + public Response graphQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphQuery(name, query, params, timeout)); + } + + @Override + public Response graphReadonlyQuery(String name, String query, Map params, long timeout) { + return appendCommand(graphCommandObjects.graphReadonlyQuery(name, query, params, timeout)); + } + + @Override + public Response graphDelete(String name) { + return appendCommand(graphCommandObjects.graphDelete(name)); + } + // RedisGraph commands + public Response waitReplicas(int replicas, long timeout) { return appendCommand(commandObjects.waitReplicas(replicas, timeout)); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index ec51f923bc..c7a1b26817 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -21,6 +21,8 @@ import redis.clients.jedis.commands.SampleKeyedCommands; import redis.clients.jedis.commands.RedisModuleCommands; import redis.clients.jedis.executors.*; +import redis.clients.jedis.graph.GraphCommandObjects; +import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.json.JsonSetParams; import redis.clients.jedis.json.Path; import redis.clients.jedis.json.Path2; @@ -45,6 +47,7 @@ public class UnifiedJedis implements JedisCommands, JedisBinaryCommands, protected final ConnectionProvider provider; protected final CommandExecutor executor; private final CommandObjects commandObjects; + private final GraphCommandObjects graphCommandObjects = new GraphCommandObjects(this); public UnifiedJedis() { this(new HostAndPort(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT)); @@ -4071,6 +4074,53 @@ public Map topkInfo(String key) { } // RedisBloom commands + // RedisGraph commands + @Override + public ResultSet graphQuery(String name, String query) { + return executeCommand(graphCommandObjects.graphQuery(name, query)); + } + + @Override + public ResultSet graphReadonlyQuery(String name, String query) { + return executeCommand(graphCommandObjects.graphReadonlyQuery(name, query)); + } + + @Override + public ResultSet graphQuery(String name, String query, long timeout) { + return executeCommand(graphCommandObjects.graphQuery(name, query, timeout)); + } + + @Override + public ResultSet graphReadonlyQuery(String name, String query, long timeout) { + return executeCommand(graphCommandObjects.graphReadonlyQuery(name, query, timeout)); + } + + @Override + public ResultSet graphQuery(String name, String query, Map params) { + return executeCommand(graphCommandObjects.graphQuery(name, query, params)); + } + + @Override + public ResultSet graphReadonlyQuery(String name, String query, Map params) { + return executeCommand(graphCommandObjects.graphReadonlyQuery(name, query, params)); + } + + @Override + public ResultSet graphQuery(String name, String query, Map params, long timeout) { + return executeCommand(graphCommandObjects.graphQuery(name, query, params, timeout)); + } + + @Override + public ResultSet graphReadonlyQuery(String name, String query, Map params, long timeout) { + return executeCommand(graphCommandObjects.graphReadonlyQuery(name, query, params, timeout)); + } + + @Override + public String graphDelete(String name) { + return executeCommand(graphCommandObjects.graphDelete(name)); + } + // RedisGraph commands + public Object sendCommand(ProtocolCommand cmd) { return executeCommand(commandObjects.commandArguments(cmd)); } diff --git a/src/main/java/redis/clients/jedis/commands/RedisModuleCommands.java b/src/main/java/redis/clients/jedis/commands/RedisModuleCommands.java index b27051daa6..af17f2a448 100644 --- a/src/main/java/redis/clients/jedis/commands/RedisModuleCommands.java +++ b/src/main/java/redis/clients/jedis/commands/RedisModuleCommands.java @@ -1,6 +1,7 @@ package redis.clients.jedis.commands; import redis.clients.jedis.bloom.RedisBloomCommands; +import redis.clients.jedis.graph.RedisGraphCommands; import redis.clients.jedis.json.RedisJsonCommands; import redis.clients.jedis.search.RediSearchCommands; import redis.clients.jedis.timeseries.RedisTimeSeriesCommands; @@ -9,6 +10,7 @@ public interface RedisModuleCommands extends RediSearchCommands, RedisJsonCommands, RedisTimeSeriesCommands, - RedisBloomCommands { + RedisBloomCommands, + RedisGraphCommands { } diff --git a/src/main/java/redis/clients/jedis/commands/RedisModulePipelineCommands.java b/src/main/java/redis/clients/jedis/commands/RedisModulePipelineCommands.java index 61ecf654de..79f468d27e 100644 --- a/src/main/java/redis/clients/jedis/commands/RedisModulePipelineCommands.java +++ b/src/main/java/redis/clients/jedis/commands/RedisModulePipelineCommands.java @@ -1,6 +1,7 @@ package redis.clients.jedis.commands; import redis.clients.jedis.bloom.RedisBloomPipelineCommands; +import redis.clients.jedis.graph.RedisGraphPipelineCommands; import redis.clients.jedis.json.RedisJsonPipelineCommands; import redis.clients.jedis.search.RediSearchPipelineCommands; import redis.clients.jedis.timeseries.RedisTimeSeriesPipelineCommands; @@ -9,6 +10,7 @@ public interface RedisModulePipelineCommands extends RediSearchPipelineCommands, RedisJsonPipelineCommands, RedisTimeSeriesPipelineCommands, - RedisBloomPipelineCommands { + RedisBloomPipelineCommands, + RedisGraphPipelineCommands { } diff --git a/src/main/java/redis/clients/jedis/graph/GraphCache.java b/src/main/java/redis/clients/jedis/graph/GraphCache.java new file mode 100644 index 0000000000..5ee78def0b --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/GraphCache.java @@ -0,0 +1,26 @@ +package redis.clients.jedis.graph; + +/** + * Store a local cache in the client, for a specific graph. Holds the labels, property names and + * relationship types. + */ +interface GraphCache { + + /** + * @param index - index of label + * @return requested label + */ + String getLabel(int index); + + /** + * @param index index of the relationship type + * @return requested relationship type + */ + String getRelationshipType(int index); + + /** + * @param index index of property name + * @return requested property + */ + String getPropertyName(int index); +} diff --git a/src/main/java/redis/clients/jedis/graph/GraphCommandObjects.java b/src/main/java/redis/clients/jedis/graph/GraphCommandObjects.java new file mode 100644 index 0000000000..be0d44157c --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/GraphCommandObjects.java @@ -0,0 +1,201 @@ +package redis.clients.jedis.graph; + +import static redis.clients.jedis.BuilderFactory.STRING; +import static redis.clients.jedis.graph.GraphProtocol.GraphKeyword.__COMPACT; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import redis.clients.jedis.Builder; +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.CommandObject; +import redis.clients.jedis.Connection; +import redis.clients.jedis.graph.GraphProtocol.GraphCommand; +import redis.clients.jedis.providers.ConnectionProvider; + +public class GraphCommandObjects { + + private final RedisGraphCommands graph; + private final Connection connection; + private final ConnectionProvider provider; + private final ConcurrentHashMap> builders = new ConcurrentHashMap<>(); + + public GraphCommandObjects(RedisGraphCommands graphCommands) { + this.graph = graphCommands; + this.connection = null; + this.provider = null; + } + + public GraphCommandObjects(Connection connection) { + this.connection = connection; + this.provider = null; + this.graph = null; + } + + public GraphCommandObjects(ConnectionProvider provider) { + this.provider = provider; + this.connection = null; + this.graph = null; + } + + // RedisGraph commands + public final CommandObject graphQuery(String name, String query) { + return new CommandObject<>(new CommandArguments(GraphCommand.QUERY).key(name).add(query).add(__COMPACT), getBuilder(name)); + } + + public final CommandObject graphReadonlyQuery(String name, String query) { + return new CommandObject<>(new CommandArguments(GraphCommand.RO_QUERY).key(name).add(query).add(__COMPACT), getBuilder(name)); + } + + public final CommandObject graphQuery(String name, String query, long timeout) { + return graphQuery(name, GraphQueryParams.queryParams(query).timeout(timeout)); + } + + public final CommandObject graphReadonlyQuery(String name, String query, long timeout) { + return graphQuery(name, GraphQueryParams.queryParams().readonly().query(query).timeout(timeout)); + } + + public final CommandObject graphQuery(String name, String query, Map params) { + return graphQuery(name, GraphQueryParams.queryParams(query).params(params)); + } + + public final CommandObject graphReadonlyQuery(String name, String query, Map params) { + return graphQuery(name, GraphQueryParams.queryParams().readonly().query(query).params(params)); + } + + public final CommandObject graphQuery(String name, String query, Map params, long timeout) { + return graphQuery(name, GraphQueryParams.queryParams(query).params(params).timeout(timeout)); + } + + public final CommandObject graphReadonlyQuery(String name, String query, Map params, long timeout) { + return graphQuery(name, GraphQueryParams.queryParams().readonly().query(query).params(params).timeout(timeout)); + } + + private CommandObject graphQuery(String name, GraphQueryParams params) { + return new CommandObject<>(params.getArguments(name), getBuilder(name)); + } + + public final CommandObject graphDelete(String name) { + return new CommandObject<>(new CommandArguments(GraphCommand.DELETE).key(name), STRING); + } + // RedisGraph commands + + private Builder getBuilder(String graphName) { + if (!builders.containsKey(graphName)) { + createBuilder(graphName); + } + return builders.get(graphName); + } + + private void createBuilder(String graphName) { + synchronized (builders) { + builders.putIfAbsent(graphName, new ResultSetBuilder(new GraphCacheImpl(graphName))); + } + } + + private class GraphCacheImpl implements GraphCache { + + private final GraphCacheList labels; + private final GraphCacheList propertyNames; + private final GraphCacheList relationshipTypes; + + public GraphCacheImpl(String graphName) { + this.labels = new GraphCacheList(graphName, "db.labels"); + this.propertyNames = new GraphCacheList(graphName, "db.propertyKeys"); + this.relationshipTypes = new GraphCacheList(graphName, "db.relationshipTypes"); + } + + @Override + public String getLabel(int index) { + return labels.getCachedData(index); + } + + @Override + public String getRelationshipType(int index) { + return relationshipTypes.getCachedData(index); + } + + @Override + public String getPropertyName(int index) { + return propertyNames.getCachedData(index); + } + } + + private class GraphCacheList { + + private final String name; + private final String query; + private final List data = new CopyOnWriteArrayList<>(); + + /** + * + * @param name - graph id + * @param procedure - exact procedure command + */ + public GraphCacheList(String name, String procedure) { + this.name = name; + this.query = "CALL " + procedure + "()"; + } + + /** + * A method to return a cached item if it is in the cache, or re-validate the cache if its + * invalidated + * + * @param index index of data item + * @return The string value of the specific procedure response, at the given index. + */ + public String getCachedData(int index) { + if (index >= data.size()) { + synchronized (data) { + if (index >= data.size()) { + getProcedureInfo(); + } + } + } + return data.get(index); + + } + + /** + * Auxiliary method to parse a procedure result set and refresh the cache + */ + private void getProcedureInfo() { + ResultSet resultSet = callProcedure(); + Iterator it = resultSet.iterator(); + List newData = new ArrayList<>(); + int i = 0; + while (it.hasNext()) { + Record record = it.next(); + if (i >= data.size()) { + newData.add(record.getString(0)); + } + i++; + } + data.addAll(newData); + } + + private ResultSet callProcedure() { + + if (graph != null) { + return graph.graphQuery(name, query); + } + + CommandObject commandObject = new CommandObject( + new CommandArguments(GraphProtocol.GraphCommand.QUERY).key(name).add(query) + .add(GraphProtocol.GraphKeyword.__COMPACT), + getBuilder(name)); + + if (connection != null) { + return connection.executeCommand(commandObject); + } else { + try (Connection provided = provider.getConnection(commandObject.getArguments())) { + return provided.executeCommand(commandObject); + } + } + } + } +} diff --git a/src/main/java/redis/clients/jedis/graph/GraphProtocol.java b/src/main/java/redis/clients/jedis/graph/GraphProtocol.java new file mode 100644 index 0000000000..26de5864de --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/GraphProtocol.java @@ -0,0 +1,48 @@ +package redis.clients.jedis.graph; + +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; + +public class GraphProtocol { + + public enum GraphCommand implements ProtocolCommand { + + QUERY, + RO_QUERY, + DELETE; + + private final byte[] raw; + + private GraphCommand() { + raw = SafeEncoder.encode("GRAPH." + name()); + } + + @Override + public byte[] getRaw() { + return raw; + } + } + + public enum GraphKeyword implements Rawable { + + CYPHER, + TIMEOUT, + __COMPACT("--COMPACT"); + + private final byte[] raw; + + private GraphKeyword() { + raw = SafeEncoder.encode(name()); + } + + private GraphKeyword(String alt) { + raw = SafeEncoder.encode(alt); + } + + @Override + public byte[] getRaw() { + return raw; + } + } +} diff --git a/src/main/java/redis/clients/jedis/graph/GraphQueryParams.java b/src/main/java/redis/clients/jedis/graph/GraphQueryParams.java new file mode 100644 index 0000000000..c181a8e823 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/GraphQueryParams.java @@ -0,0 +1,90 @@ +package redis.clients.jedis.graph; + +import java.util.HashMap; +import java.util.Map; +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.exceptions.JedisException; +import redis.clients.jedis.graph.GraphProtocol.GraphCommand; +import redis.clients.jedis.graph.GraphProtocol.GraphKeyword; +import redis.clients.jedis.params.IParams; + +public class GraphQueryParams implements IParams { + + private boolean readonly; + private String query; + private Map params; + private Long timeout; + + /** + * Query string must be set later. + */ + public GraphQueryParams() { + } + + /** + * Query string must be set later. + */ + public static GraphQueryParams queryParams() { + return new GraphQueryParams(); + } + + public GraphQueryParams(String query) { + this.query = query; + } + + public static GraphQueryParams queryParams(String query) { + return new GraphQueryParams(query); + } + + public GraphQueryParams readonly() { + return readonly(true); + } + + public GraphQueryParams readonly(boolean readonly) { + this.readonly = readonly; + return this; + } + + public GraphQueryParams query(String queryStr) { + this.query = queryStr; + return this; + } + + public GraphQueryParams params(Map params) { + this.params = params; + return this; + } + + public GraphQueryParams addParam(String key, Object value) { + if (this.params == null) this.params = new HashMap<>(); + this.params.put(key, value); + return this; + } + + public GraphQueryParams timeout(long timeout) { + this.timeout = timeout; + return this; + } + + @Override + public void addParams(CommandArguments args) { + if (query == null) throw new JedisException("Query string must be set."); + + if (params == null) { + args.add(query); + } else { + args.add(RedisGraphQueryUtil.prepareQuery(query, params)); + } + + args.add(GraphKeyword.__COMPACT); + + if (timeout != null) { + args.add(GraphKeyword.TIMEOUT).add(timeout).blocking(); + } + } + + public CommandArguments getArguments(String graphName) { + return new CommandArguments(!readonly ? GraphCommand.QUERY : GraphCommand.RO_QUERY) + .key(graphName).addParams(this); + } +} diff --git a/src/main/java/redis/clients/jedis/graph/Header.java b/src/main/java/redis/clients/jedis/graph/Header.java new file mode 100644 index 0000000000..f69b16df09 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/Header.java @@ -0,0 +1,13 @@ +package redis.clients.jedis.graph; + +import java.util.List; + +/** + * Query response header interface. Represents the response schema (column names and types) + */ +public interface Header { + + List getSchemaTypes(); + + List getSchemaNames(); +} diff --git a/src/main/java/redis/clients/jedis/graph/Record.java b/src/main/java/redis/clients/jedis/graph/Record.java new file mode 100644 index 0000000000..46e6abb58d --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/Record.java @@ -0,0 +1,76 @@ +package redis.clients.jedis.graph; + +import java.util.List; + +/** + * Container for RedisGraph result values. + * + * List records are returned from RedisGraph statement execution, contained within a ResultSet. + */ +public interface Record { + + /** + * The value at the given field index + * + * @param index field index + * + * @return the value + */ + T getValue(int index); + + /** + * The value at the given field + * + * @param key header key + * + * @return the value + */ + T getValue(String key); + + /** + * The value at the given field index (represented as String) + * + * @param index + * @return string representation of the value + */ + String getString(int index); + + /** + * The value at the given field (represented as String) + * + * @param key header key + * + * @return string representation of the value + */ + String getString(String key); + + /** + * The keys of the record + * + * @return list of the record key + */ + List keys(); + + /** + * The values of the record + * + * @return list of the record values + */ + List values(); + + /** + * Check if the record header contains the given key + * + * @param key header key + * + * @return true if the the key exists + */ + boolean containsKey(String key); + + /** + * The number of fields in this record + * + * @return the number of fields + */ + int size(); +} diff --git a/src/main/java/redis/clients/jedis/graph/RedisGraphCommands.java b/src/main/java/redis/clients/jedis/graph/RedisGraphCommands.java new file mode 100644 index 0000000000..68cf747169 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/RedisGraphCommands.java @@ -0,0 +1,95 @@ +package redis.clients.jedis.graph; + +import java.util.Map; + +public interface RedisGraphCommands { + + /** + * Execute a Cypher query. + * + * @param name a graph to perform the query on + * @param query Cypher query + * @return a result set + */ + ResultSet graphQuery(String name, String query); + + /** + * Execute a Cypher read-only query. + * + * @param name a graph to perform the query on + * @param query Cypher query + * @return a result set + */ + ResultSet graphReadonlyQuery(String name, String query); + + /** + * Execute a Cypher query with timeout. + * + * @param name a graph to perform the query on + * @param query Cypher query + * @param timeout + * @return a result set + */ + ResultSet graphQuery(String name, String query, long timeout); + + /** + * Execute a Cypher read-only query with timeout. + * + * @param name a graph to perform the query on + * @param query Cypher query + * @param timeout + * @return a result set + */ + ResultSet graphReadonlyQuery(String name, String query, long timeout); + + /** + * Executes a cypher query with parameters. + * + * @param name a graph to perform the query on. + * @param query Cypher query. + * @param params parameters map. + * @return a result set. + */ + ResultSet graphQuery(String name, String query, Map params); + + /** + * Executes a cypher read-only query with parameters. + * + * @param name a graph to perform the query on. + * @param query Cypher query. + * @param params parameters map. + * @return a result set. + */ + ResultSet graphReadonlyQuery(String name, String query, Map params); + + /** + * Executes a cypher query with parameters and timeout. + * + * @param name a graph to perform the query on. + * @param query Cypher query. + * @param params parameters map. + * @param timeout + * @return a result set. + */ + ResultSet graphQuery(String name, String query, Map params, long timeout); + + /** + * Executes a cypher read-only query with parameters and timeout. + * + * @param name a graph to perform the query on. + * @param query Cypher query. + * @param params parameters map. + * @param timeout + * @return a result set. + */ + ResultSet graphReadonlyQuery(String name, String query, Map params, long timeout); + + /** + * Deletes the entire graph + * + * @param name graph to delete + * @return delete running time statistics + */ + String graphDelete(String name); + +} diff --git a/src/main/java/redis/clients/jedis/graph/RedisGraphPipelineCommands.java b/src/main/java/redis/clients/jedis/graph/RedisGraphPipelineCommands.java new file mode 100644 index 0000000000..f8933206d6 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/RedisGraphPipelineCommands.java @@ -0,0 +1,26 @@ +package redis.clients.jedis.graph; + +import java.util.Map; +import redis.clients.jedis.Response; + +public interface RedisGraphPipelineCommands { + + Response graphQuery(String name, String query); + + Response graphReadonlyQuery(String name, String query); + + Response graphQuery(String name, String query, long timeout); + + Response graphReadonlyQuery(String name, String query, long timeout); + + Response graphQuery(String name, String query, Map params); + + Response graphReadonlyQuery(String name, String query, Map params); + + Response graphQuery(String name, String query, Map params, long timeout); + + Response graphReadonlyQuery(String name, String query, Map params, long timeout); + + Response graphDelete(String name); + +} diff --git a/src/main/java/redis/clients/jedis/graph/RedisGraphQueryUtil.java b/src/main/java/redis/clients/jedis/graph/RedisGraphQueryUtil.java new file mode 100644 index 0000000000..a98cb49745 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/RedisGraphQueryUtil.java @@ -0,0 +1,106 @@ +package redis.clients.jedis.graph; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RedisGraphQueryUtil { + + public static final List DUMMY_LIST = Collections.emptyList(); + public static final Map> DUMMY_MAP = Collections.emptyMap(); + public static final String COMPACT_STRING = "--COMPACT"; + public static final String TIMEOUT_STRING = "TIMEOUT"; + + private RedisGraphQueryUtil() { + } + + /** + * Prepare and formats a query and query arguments + * + * @param query - query + * @param params - query parameters + * @return query with parameters header + */ + public static String prepareQuery(String query, Map params) { + StringBuilder sb = new StringBuilder("CYPHER "); + for (Map.Entry entry : params.entrySet()) { + sb + .append(entry.getKey()) + .append('=') + .append(valueToString(entry.getValue())) + .append(' '); + } + sb.append(query); + return sb.toString(); + } + + private static String valueToString(Object value) { + if (value == null) { + return "null"; + } + + if (value instanceof String) { + return quoteString((String) value); + } + if (value instanceof Character) { + return quoteString(((Character) value).toString()); + } + + if (value instanceof Object[]) { + return arrayToString((Object[]) value); + } + if (value instanceof List) { + return arrayToString((List) value); + } + return value.toString(); + } + + private static String quoteString(String str) { + StringBuilder sb = new StringBuilder(str.length() + 12); + sb.append('"'); + sb.append(str.replace("\"", "\\\"")); + sb.append('"'); + return sb.toString(); + } + + private static String arrayToString(Object[] arr) { +// StringBuilder sb = new StringBuilder().append('['); +// sb.append(String.join(", ", Arrays.stream(arr).map(RedisGraphQueryUtil::valueToString).collect(Collectors.toList()))); +// sb.append(']'); +// return sb.toString(); + return arrayToString(Arrays.asList(arr)); + } + + private static String arrayToString(List arr) { + StringBuilder sb = new StringBuilder().append('['); + sb.append(String.join(", ", arr.stream().map(RedisGraphQueryUtil::valueToString).collect(Collectors.toList()))); + sb.append(']'); + return sb.toString(); + } + +// public static String prepareProcedure(String procedure, List args, Map> kwargs) { +// args = args.stream().map(RedisGraphQueryUtil::quoteString).collect(Collectors.toList()); +// StringBuilder queryStringBuilder = new StringBuilder(); +// queryStringBuilder.append("CALL ").append(procedure).append('('); +// int i = 0; +// for (; i < args.size() - 1; i++) { +// queryStringBuilder.append(args.get(i)).append(','); +// } +// if (i == args.size() - 1) { +// queryStringBuilder.append(args.get(i)); +// } +// queryStringBuilder.append(')'); +// List kwargsList = kwargs.getOrDefault("y", null); +// if (kwargsList != null) { +// i = 0; +// for (; i < kwargsList.size() - 1; i++) { +// queryStringBuilder.append(kwargsList.get(i)).append(','); +// +// } +// queryStringBuilder.append(kwargsList.get(i)); +// } +// return queryStringBuilder.toString(); +// } +} diff --git a/src/main/java/redis/clients/jedis/graph/ResultSet.java b/src/main/java/redis/clients/jedis/graph/ResultSet.java new file mode 100644 index 0000000000..da0a5bcce1 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/ResultSet.java @@ -0,0 +1,20 @@ +package redis.clients.jedis.graph; + +/** + * Hold a query result + */ +public interface ResultSet extends Iterable { + + public enum ColumnType { + UNKNOWN, + SCALAR, + NODE, + RELATION + } + + int size(); + + Header getHeader(); + + Statistics getStatistics(); +} diff --git a/src/main/java/redis/clients/jedis/graph/ResultSetBuilder.java b/src/main/java/redis/clients/jedis/graph/ResultSetBuilder.java new file mode 100644 index 0000000000..efca7391a3 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/ResultSetBuilder.java @@ -0,0 +1,646 @@ +package redis.clients.jedis.graph; + +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import redis.clients.jedis.Builder; +import redis.clients.jedis.BuilderFactory; +import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.exceptions.JedisException; +import redis.clients.jedis.graph.entities.Edge; +import redis.clients.jedis.graph.entities.GraphEntity; +import redis.clients.jedis.graph.entities.Node; +import redis.clients.jedis.graph.entities.Path; +import redis.clients.jedis.graph.entities.Point; +import redis.clients.jedis.util.SafeEncoder; + +class ResultSetBuilder extends Builder { + + private final GraphCache graphCache; + + ResultSetBuilder(GraphCache cache) { + this.graphCache = cache; + } + + @Override + public ResultSet build(Object data) { + List rawResponse = (List) data; + // If a run-time error occurred, the last member of the rawResponse will be a + // JedisDataException. + if (rawResponse.get(rawResponse.size() - 1) instanceof JedisDataException) { + throw (JedisDataException) rawResponse.get(rawResponse.size() - 1); + } +// +// HeaderImpl header = parseHeader(rawResponse.get(0)); +// List records = parseRecords(header, rawResponse.get(1)); +// StatisticsImpl statistics = parseStatistics(rawResponse.get(2)); +// return new ResultSetImpl(header, records, statistics); + + final Object headerObject; + final Object recordsObject; + final Object statisticsObject; + + if (rawResponse.size() == 1) { + headerObject = emptyList(); + recordsObject = emptyList(); + statisticsObject = rawResponse.get(0); + } else if (rawResponse.size() == 3) { + headerObject = rawResponse.get(0); + recordsObject = rawResponse.get(1); + statisticsObject = rawResponse.get(2); + } else { + throw new JedisException("Unrecognized graph response format."); + } + + HeaderImpl header = parseHeader(headerObject); + List records = parseRecords(header, recordsObject); + StatisticsImpl statistics = parseStatistics(statisticsObject); + return new ResultSetImpl(header, records, statistics); + } + + private class ResultSetImpl implements ResultSet { + + private final Header header; + private final List results; + private final Statistics statistics; + + private ResultSetImpl(Header header, List results, Statistics statistics) { + this.header = header; + this.results = results; + this.statistics = statistics; + } + + @Override + public Header getHeader() { + return header; + } + + @Override + public Statistics getStatistics() { + return statistics; + } + + @Override + public int size() { + return results.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ResultSetImpl)) { + return false; + } + ResultSetImpl resultSet = (ResultSetImpl) o; + return Objects.equals(getHeader(), resultSet.getHeader()) + && Objects.equals(getStatistics(), resultSet.getStatistics()) + && Objects.equals(results, resultSet.results); + } + + @Override + public int hashCode() { + return Objects.hash(getHeader(), getStatistics(), results); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ResultSetImpl{"); + sb.append("header=").append(header); + sb.append(", statistics=").append(statistics); + sb.append(", results=").append(results); + sb.append('}'); + return sb.toString(); + } + + @Override + public Iterator iterator() { + return results.iterator(); + } + } + + @SuppressWarnings("unchecked") + private List parseRecords(Header header, Object data) { + List> rawResultSet = (List>) data; + List results = new ArrayList<>(); + if (rawResultSet == null || rawResultSet.isEmpty()) { + return results; + } else { + // 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 + ResultSet.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 RecordImpl(header.getSchemaNames(), parsedRow); + results.add(record); + } + } + return results; + } + + /** + * @param rawNodeData - raw node object in the form of list of object rawNodeData.get(0) - id + * (long) rawNodeData.get(1) - a list y which contains the labels of this node. Each entry is a + * label id from the type of long rawNodeData.get(2) - a list which contains the properties of the + * node. + * @return Node object + */ + @SuppressWarnings("unchecked") + private Node deserializeNode(List rawNodeData) { + Node node = new Node(); + deserializeGraphEntityId(node, rawNodeData.get(0)); + List labelsIndices = (List) rawNodeData.get(1); + for (Long labelIndex : labelsIndices) { + String label = graphCache.getLabel(labelIndex.intValue()); + node.addLabel(label); + } + deserializeGraphEntityProperties(node, (List>) rawNodeData.get(2)); + + return node; + + } + + /** + * @param graphEntity graph entity + * @param rawEntityId raw representation of entity id to be set to the graph entity + */ + private void deserializeGraphEntityId(GraphEntity graphEntity, Object rawEntityId) { + long id = (Long) rawEntityId; + graphEntity.setId(id); + } + + /** + * @param rawEdgeData - a list of objects rawEdgeData[0] - edge id rawEdgeData[1] - edge + * relationship type rawEdgeData[2] - edge source rawEdgeData[3] - edge destination rawEdgeData[4] + * - edge properties + * @return Edge object + */ + @SuppressWarnings("unchecked") + private Edge deserializeEdge(List rawEdgeData) { + Edge edge = new Edge(); + deserializeGraphEntityId(edge, rawEdgeData.get(0)); + + String relationshipType = graphCache.getRelationshipType(((Long) rawEdgeData.get(1)).intValue()); + edge.setRelationshipType(relationshipType); + + edge.setSource((long) rawEdgeData.get(2)); + edge.setDestination((long) rawEdgeData.get(3)); + + deserializeGraphEntityProperties(edge, (List>) rawEdgeData.get(4)); + + return edge; + } + + /** + * @param entity graph entity for adding the properties to + * @param rawProperties raw representation of a list of graph entity properties. Each entry is a + * list (rawProperty) is a raw representation of property, as follows: rawProperty.get(0) - + * property key rawProperty.get(1) - property type rawProperty.get(2) - property value + */ + private void deserializeGraphEntityProperties(GraphEntity entity, List> rawProperties) { + + for (List rawProperty : rawProperties) { + String name = graphCache.getPropertyName(((Long) rawProperty.get(0)).intValue()); + + // trimmed for getting to value using deserializeScalar + List propertyScalar = rawProperty.subList(1, rawProperty.size()); + + entity.addProperty(name, deserializeScalar(propertyScalar)); + } + + } + + /** + * @param rawScalarData - a list of object. list[0] is the scalar type, list[1] is the scalar + * value + * @return value of the specific scalar type + */ + @SuppressWarnings("unchecked") + 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(SafeEncoder.encode((byte[]) obj)); + case DOUBLE: + return Double.parseDouble(SafeEncoder.encode((byte[]) obj)); + case INTEGER: + return (Long) obj; + case STRING: + return SafeEncoder.encode((byte[]) 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(BuilderFactory.DOUBLE_LIST.build(rawScalarData)); + } + + @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 = SafeEncoder.encode((byte[]) 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; + } + + /** + * Auxiliary function to retrieve scalar types + * + * @param rawScalarType + * @return scalar type + */ + private ScalarType getValueTypeFromObject(Object rawScalarType) { + return getScalarType(((Long) rawScalarType).intValue()); + } + + private static enum ScalarType { + UNKNOWN, + NULL, + STRING, + INTEGER, // 64 bit long. + BOOLEAN, + DOUBLE, + ARRAY, + EDGE, + NODE, + PATH, + MAP, + POINT; + } + + private static final ScalarType[] SCALAR_TYPES = ScalarType.values(); + + private static ScalarType getScalarType(int index) { + try { + return SCALAR_TYPES[index]; + } catch (IndexOutOfBoundsException e) { + throw new JedisException("Unrecognized response type"); + } + } + + private class RecordImpl implements Record { + + private final List header; + private final List values; + + public RecordImpl(List header, List values) { + this.header = header; + this.values = values; + } + + @Override + public T getValue(int index) { + return (T) this.values.get(index); + } + + @Override + public T getValue(String key) { + return getValue(this.header.indexOf(key)); + } + + @Override + public String getString(int index) { + return this.values.get(index).toString(); + } + + @Override + public String getString(String key) { + return getString(this.header.indexOf(key)); + } + + @Override + public List keys() { + return header; + } + + @Override + public List values() { + return this.values; + } + + @Override + public boolean containsKey(String key) { + return this.header.contains(key); + } + + @Override + public int size() { + return this.header.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RecordImpl)) { + return false; + } + RecordImpl record = (RecordImpl) o; + return Objects.equals(header, record.header) + && Objects.equals(values, record.values); + } + + @Override + public int hashCode() { + return Objects.hash(header, values); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Record{"); + sb.append("values=").append(values); + sb.append('}'); + return sb.toString(); + } + } + + private static final ResultSet.ColumnType[] COLUMN_TYPES = ResultSet.ColumnType.values(); + + private class HeaderImpl implements Header { + + private final List schemaTypes; + private final List schemaNames; + + private HeaderImpl() { + this.schemaTypes = emptyList(); + this.schemaNames = emptyList(); + } + + private HeaderImpl(List schemaTypes, List schemaNames) { + this.schemaTypes = schemaTypes; + this.schemaNames = schemaNames; + } + + /** + * @return a list of column names, ordered by they appearance in the query + */ + @Override + public List getSchemaNames() { + return schemaNames; + } + + /** + * @return a list of column types, ordered by they appearance in the query + */ + @Override + public List getSchemaTypes() { + return schemaTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HeaderImpl)) { + return false; + } + HeaderImpl header = (HeaderImpl) o; + return Objects.equals(getSchemaTypes(), header.getSchemaTypes()) + && Objects.equals(getSchemaNames(), header.getSchemaNames()); + } + + @Override + public int hashCode() { + return Objects.hash(getSchemaTypes(), getSchemaNames()); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("HeaderImpl{"); + sb.append("schemaTypes=").append(schemaTypes); + sb.append(", schemaNames=").append(schemaNames); + sb.append('}'); + return sb.toString(); + } + } + + private HeaderImpl parseHeader(Object data) { + if (data == null) { + return new HeaderImpl(); + } + + 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(SafeEncoder.encode((byte[]) tuple.get(1))); + } + return new HeaderImpl(types, texts); + } + + private class StatisticsImpl implements Statistics { + + private final Map statistics; + + private StatisticsImpl(Map statistics) { + this.statistics = statistics; + } + + /** + * + * @param label the requested statistic label as key + * @return a string with the value, if key exists, null otherwise + */ + public String getStringValue(String label) { + return statistics.get(label); + } + + /** + * + * @param label the requested statistic label as key + * @return a string with the value, if key exists, 0 otherwise + */ + private int getIntValue(String label) { + String value = getStringValue(label); + return value == null ? 0 : Integer.parseInt(value); + } + + /** + * + * @return number of nodes created after query execution + */ + @Override + public int nodesCreated() { + return getIntValue("Nodes created"); + } + + /** + * + * @return number of nodes deleted after query execution + */ + @Override + public int nodesDeleted() { + return getIntValue("Nodes deleted"); + } + + /** + * + * @return number of indices added after query execution + */ + @Override + public int indicesCreated() { + return getIntValue("Indices created"); + } + + @Override + public int indicesDeleted() { + return getIntValue("Indices deleted"); + } + + /** + * + * @return number of labels added after query execution + */ + @Override + public int labelsAdded() { + return getIntValue("Labels added"); + } + + /** + * + * @return number of relationship deleted after query execution + */ + @Override + public int relationshipsDeleted() { + return getIntValue("Relationships deleted"); + } + + /** + * + * @return number of relationship created after query execution + */ + @Override + public int relationshipsCreated() { + return getIntValue("Relationships created"); + } + + /** + * + * @return number of properties set after query execution + */ + @Override + public int propertiesSet() { + return getIntValue("Properties set"); + } + + /** + * + * @return The execution plan was cached on RedisGraph. + */ + @Override + public boolean cachedExecution() { + return "1".equals(getStringValue("Cached execution")); + } + + @Override + public String queryIntervalExecutionTime() { + return getStringValue("Query internal execution time"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof StatisticsImpl)) { + return false; + } + StatisticsImpl that = (StatisticsImpl) o; + return Objects.equals(statistics, that.statistics); + } + + @Override + public int hashCode() { + return Objects.hash(statistics); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Statistics{"); + sb.append(statistics); + sb.append('}'); + return sb.toString(); + } + } + + private StatisticsImpl parseStatistics(Object data) { + Map map = ((List) data).stream() + .map(SafeEncoder::encode).map(s -> s.split(": ")) + .collect(Collectors.toMap(sa -> sa[0], sa -> sa[1])); + return new StatisticsImpl(map); + } + +} diff --git a/src/main/java/redis/clients/jedis/graph/Statistics.java b/src/main/java/redis/clients/jedis/graph/Statistics.java new file mode 100644 index 0000000000..f6ad5240f9 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/Statistics.java @@ -0,0 +1,24 @@ +package redis.clients.jedis.graph; + +public interface Statistics { + + int nodesCreated(); + + int nodesDeleted(); + + int indicesCreated(); + + int indicesDeleted(); + + int labelsAdded(); + + int relationshipsDeleted(); + + int relationshipsCreated(); + + int propertiesSet(); + + boolean cachedExecution(); + + String queryIntervalExecutionTime(); +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/Edge.java b/src/main/java/redis/clients/jedis/graph/entities/Edge.java new file mode 100644 index 0000000000..406967fee7 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/Edge.java @@ -0,0 +1,92 @@ +package redis.clients.jedis.graph.entities; + +import java.util.Objects; + +/** + * A class represent an edge (graph entity). In addition to the base class id and properties, an edge shows its source, + * destination and relationship type + */ +public class Edge extends GraphEntity { + + //members + private String relationshipType; + private long source; + private long destination; + + + //getters & setters + + /** + * @return the edge relationship type + */ + public String getRelationshipType() { + return relationshipType; + } + + /** + * @param relationshipType - the relationship type to be set. + */ + public void setRelationshipType(String relationshipType) { + this.relationshipType = relationshipType; + } + + + /** + * @return The id of the source node + */ + public long getSource() { + return source; + } + + /** + * @param source - The id of the source node to be set + */ + public void setSource(long source) { + this.source = source; + } + + /** + * + * @return the id of the destination node + */ + public long getDestination() { + return destination; + } + + /** + * + * @param destination - The id of the destination node to be set + */ + public void setDestination(long destination) { + this.destination = destination; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Edge)) return false; + if (!super.equals(o)) return false; + Edge edge = (Edge) o; + return source == edge.source && + destination == edge.destination && + Objects.equals(relationshipType, edge.relationshipType); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), relationshipType, source, destination); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Edge{"); + sb.append("relationshipType='").append(relationshipType).append('\''); + sb.append(", source=").append(source); + sb.append(", destination=").append(destination); + sb.append(", id=").append(id); + sb.append(", propertyMap=").append(propertyMap); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/GraphEntity.java b/src/main/java/redis/clients/jedis/graph/entities/GraphEntity.java new file mode 100644 index 0000000000..6af7321e61 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/GraphEntity.java @@ -0,0 +1,96 @@ +package redis.clients.jedis.graph.entities; + +import java.util.*; + +/** + * This is an abstract class for representing a graph entity. A graph entity has an id and a set of + * properties. The properties are mapped and accessed by their names. + */ +public abstract class GraphEntity { + + protected long id; + protected final Map> propertyMap = new HashMap<>(); + + /** + * @return entity id + */ + public long getId() { + return id; + } + + /** + * @param id - entity id to be set + */ + public void setId(long id) { + this.id = id; + } + + /** + * Adds a property to the entity, by composing name, type and value to a property object + * + * @param name + * @param value + */ + public void addProperty(String name, Object value) { + addProperty(new Property(name, value)); + } + + /** + * @return Entity's property names, as a Set + */ + public Set getEntityPropertyNames() { + return propertyMap.keySet(); + } + + /** + * Add a property to the entity + * + * @param property + */ + public void addProperty(Property property) { + propertyMap.put(property.getName(), property); + } + + /** + * @return number of properties + */ + public int getNumberOfProperties() { + return propertyMap.size(); + } + + /** + * @param propertyName - property name as lookup key (String) + * @return property object, or null if key is not found + */ + public Property getProperty(String propertyName) { + return propertyMap.get(propertyName); + } + + /** + * @param name - the name of the property to be removed + */ + public void removeProperty(String name) { + propertyMap.remove(name); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GraphEntity)) { + return false; + } + GraphEntity that = (GraphEntity) o; + return id == that.id + && Objects.equals(propertyMap, that.propertyMap); + } + + @Override + public int hashCode() { + return Objects.hash(id, propertyMap); + } + + @Override + public abstract String toString(); +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/Node.java b/src/main/java/redis/clients/jedis/graph/entities/Node.java new file mode 100644 index 0000000000..36cf9230de --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/Node.java @@ -0,0 +1,70 @@ +package redis.clients.jedis.graph.entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * * A class represent an node (graph entity). In addition to the base class id and properties, a node has labels. + */ +public class Node extends GraphEntity { + + //members + private final List labels = new ArrayList<>(); + + /** + * @param label - a label to be add + */ + public void addLabel(String label) { + labels.add(label); + } + + /** + * @param label - a label to be removed + */ + public void removeLabel(String label) { + labels.remove(label); + } + + /** + * @param index - label index + * @return the property label + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= getNumberOfLabels()}) + */ + public String getLabel(int index){ + return labels.get(index); + } + + /** + * + * @return the number of labels + */ + public int getNumberOfLabels() { + return labels.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Node)) return false; + if (!super.equals(o)) return false; + Node node = (Node) o; + return Objects.equals(labels, node.labels); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), labels); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Node{"); + sb.append("labels=").append(labels); + sb.append(", id=").append(id); + sb.append(", propertyMap=").append(propertyMap); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/Path.java b/src/main/java/redis/clients/jedis/graph/entities/Path.java new file mode 100644 index 0000000000..ff6471a3db --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/Path.java @@ -0,0 +1,117 @@ +package redis.clients.jedis.graph.entities; + +import java.util.List; +import java.util.Objects; + +/** + * This class represents a path in the graph. + */ +public final class Path { + + private final List nodes; + private final List edges; + + + /** + * Parametrized constructor + * @param nodes - List of nodes. + * @param edges - List of edges. + */ + public Path(List nodes, List edges) { + this.nodes = nodes; + this.edges = edges; + } + + /** + * Returns the nodes of the path. + * @return List of nodes. + */ + public List getNodes() { + return nodes; + } + + /** + * Returns the edges of the path. + * @return List of edges. + */ + public List getEdges() { + return edges; + } + + /** + * Returns the length of the path - number of edges. + * @return Number of edges. + */ + public int length() { + return edges.size(); + } + + /** + * Return the number of nodes in the path. + * @return Number of nodes. + */ + public int nodeCount(){ + return nodes.size(); + } + + /** + * Returns the first node in the path. + * @return First nodes in the path. + * @throws IndexOutOfBoundsException if the path is empty. + */ + public Node firstNode(){ + return nodes.get(0); + } + + /** + * Returns the last node in the path. + * @return Last nodes in the path. + * @throws IndexOutOfBoundsException if the path is empty. + */ + public Node lastNode(){ + return nodes.get(nodes.size() - 1); + } + + /** + * Returns a node with specified index in the path. + * @return Node. + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= nodesCount()}) + */ + public Node getNode(int index){ + return nodes.get(index); + } + + /** + * Returns an edge with specified index in the path. + * @return Edge. + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= length()}) + */ + public Edge getEdge(int index){ + return edges.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Path path = (Path) o; + return Objects.equals(nodes, path.nodes) && + Objects.equals(edges, path.edges); + } + + @Override + public int hashCode() { + return Objects.hash(nodes, edges); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Path{"); + sb.append("nodes=").append(nodes); + sb.append(", edges=").append(edges); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/Point.java b/src/main/java/redis/clients/jedis/graph/entities/Point.java new file mode 100644 index 0000000000..552d141c52 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/Point.java @@ -0,0 +1,62 @@ +package redis.clients.jedis.graph.entities; + +import java.util.List; +import java.util.Objects; + +/** + * This class represents a (geographical) point in the graph. + */ +public final 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; + } + + /** + * @param values {@code [latitude, 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; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof Point)) return false; + Point o = (Point) other; + return Math.abs(latitude - o.latitude) < EPSILON && + Math.abs(longitude - o.longitude) < EPSILON; + } + + @Override + public int hashCode() { + return Objects.hash(latitude, longitude); + } + + @Override + public String toString() { + return "Point{latitude=" + latitude + ", longitude=" + longitude + "}"; + } +} diff --git a/src/main/java/redis/clients/jedis/graph/entities/Property.java b/src/main/java/redis/clients/jedis/graph/entities/Property.java new file mode 100644 index 0000000000..9eae0ef699 --- /dev/null +++ b/src/main/java/redis/clients/jedis/graph/entities/Property.java @@ -0,0 +1,61 @@ +package redis.clients.jedis.graph.entities; + +import java.util.Objects; + +/** + * A Graph entity property. Has a name, type, and value + * + * @param + */ +public class Property { + + private final String name; + private final T value; + + public Property(String name, T value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public T getValue() { + return value; + } + + private boolean valueEquals(Object value1, Object value2) { + if (value1 instanceof Integer) value1 = ((Integer) value1).longValue(); + if (value2 instanceof Integer) value2 = ((Integer) value2).longValue(); + return Objects.equals(value1, value2); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Property)) return false; + Property property = (Property) o; + return Objects.equals(name, property.name) + && valueEquals(value, property.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + /** + * Default toString implementation + * + * @return + */ + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Property{"); + sb.append("name='").append(name).append('\''); + sb.append(", value=").append(value); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java b/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java index f061fe8ad3..52c07e155e 100644 --- a/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java @@ -4,7 +4,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import redis.clients.jedis.Connection; import redis.clients.jedis.HostAndPort; @@ -12,20 +11,19 @@ import redis.clients.jedis.Protocol; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.exceptions.JedisConnectionException; -import redis.clients.jedis.providers.PooledConnectionProvider; public abstract class RedisModuleCommandsTestBase { protected static final String address = System.getProperty("modulesDocker", Protocol.DEFAULT_HOST + ':' + 6479); protected static final HostAndPort hnp = HostAndPort.from(address); - private static final PooledConnectionProvider provider = new PooledConnectionProvider(hnp); protected UnifiedJedis client; public RedisModuleCommandsTestBase() { super(); } + // BeforeClass public static void prepare() { try (Connection connection = new Connection(hnp)) { assumeTrue("No Redis running on 6479 port.", connection.ping()); @@ -36,16 +34,16 @@ public static void prepare() { @Before public void setUp() { - try (Jedis jedis = createJedis()) { + try (Jedis jedis = new Jedis(hnp)) { jedis.flushAll(); } - client = new UnifiedJedis(provider); + client = new UnifiedJedis(hnp); + } + + @After + public void tearDown() throws Exception { + client.close(); } -// -// @After -// public void tearDown() throws Exception { -// client.close(); -// } // // public static void tearDown() { // client.close(); @@ -54,8 +52,4 @@ public void setUp() { protected static Connection createConnection() { return new Connection(hnp); } - - protected static Jedis createJedis() { - return new Jedis(hnp); - } } diff --git a/src/test/java/redis/clients/jedis/modules/RedisModulesPipelineTest.java b/src/test/java/redis/clients/jedis/modules/RedisModulesPipelineTest.java index 5555154ce1..446329488e 100644 --- a/src/test/java/redis/clients/jedis/modules/RedisModulesPipelineTest.java +++ b/src/test/java/redis/clients/jedis/modules/RedisModulesPipelineTest.java @@ -71,6 +71,7 @@ public void search() { Response dropIndexDD = p.ftDropIndexDD(index); p.sync(); + c.close(); assertEquals("OK", create.get()); assertEquals("OK", alter.get()); @@ -94,8 +95,6 @@ public void search() { Map> expected = new HashMap<>(); expected.put("bar", Collections.singletonList("foo")); assertEquals(expected, synDump.get()); - - c.close(); } @Test diff --git a/src/test/java/redis/clients/jedis/modules/bloom/BloomTest.java b/src/test/java/redis/clients/jedis/modules/bloom/BloomTest.java index 2a3286d812..2df2f733ec 100644 --- a/src/test/java/redis/clients/jedis/modules/bloom/BloomTest.java +++ b/src/test/java/redis/clients/jedis/modules/bloom/BloomTest.java @@ -8,7 +8,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -23,11 +22,11 @@ public class BloomTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } @Test public void reserveBasic() { diff --git a/src/test/java/redis/clients/jedis/modules/bloom/CMSTest.java b/src/test/java/redis/clients/jedis/modules/bloom/CMSTest.java index 0e983dd289..960729d9a8 100644 --- a/src/test/java/redis/clients/jedis/modules/bloom/CMSTest.java +++ b/src/test/java/redis/clients/jedis/modules/bloom/CMSTest.java @@ -8,7 +8,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -24,11 +23,11 @@ public class CMSTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } @Test public void testInitByDim() { diff --git a/src/test/java/redis/clients/jedis/modules/bloom/CuckooTest.java b/src/test/java/redis/clients/jedis/modules/bloom/CuckooTest.java index d3add09bba..bec7b90500 100644 --- a/src/test/java/redis/clients/jedis/modules/bloom/CuckooTest.java +++ b/src/test/java/redis/clients/jedis/modules/bloom/CuckooTest.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Map; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -27,11 +26,11 @@ public class CuckooTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } @Test public void testReservationCapacityOnly() { diff --git a/src/test/java/redis/clients/jedis/modules/bloom/TopKTest.java b/src/test/java/redis/clients/jedis/modules/bloom/TopKTest.java index e9d750181a..d445f6589c 100644 --- a/src/test/java/redis/clients/jedis/modules/bloom/TopKTest.java +++ b/src/test/java/redis/clients/jedis/modules/bloom/TopKTest.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.TreeSet; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -18,11 +17,11 @@ public class TopKTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } @Test public void createTopKFilter() { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java new file mode 100644 index 0000000000..906868b835 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java @@ -0,0 +1,750 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.*; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; +import redis.clients.jedis.graph.Header; +import redis.clients.jedis.graph.Record; +import redis.clients.jedis.graph.ResultSet; +import redis.clients.jedis.graph.entities.*; + +import redis.clients.jedis.modules.RedisModuleCommandsTestBase; + +public class GraphAPITest extends RedisModuleCommandsTestBase { + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } + + @After + @Override + public void tearDown() throws Exception { + client.graphDelete("social"); + super.tearDown(); + } +// +// @Test +// public void testCreateNode() { +// // Create a node +// ResultSet resultSet = client.graphQuery("social", "CREATE ({name:'roi',age:32})"); +// +// assertEquals(1, resultSet.getStatistics().nodesCreated()); +// assertEquals(0, resultSet.getStatistics().nodesDeleted()); +// assertEquals(0, resultSet.getStatistics().relationshipsCreated()); +// assertEquals(0, resultSet.getStatistics().relationshipsDeleted()); +// assertEquals(2, resultSet.getStatistics().propertiesSet()); +// assertTrue(resultSet.getStatistics().queryIntervalExecutionTime() > 0); +// +// assertFalse(resultSet.hasNext()); +// +// try { +// resultSet.iterator().next(); +// fail(); +// } catch (NoSuchElementException ignored) { +// } +// } +// +// @Test +// public void testCreateLabeledNode() { +// // Create a node with a label +// ResultSet resultSet = client.graphQuery("social", "CREATE (:human{name:'danny',age:12})"); +// assertFalse(resultSet.hasNext()); +// assertEquals("1", resultSet.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertEquals("2", resultSet.getStatistics().getStringValue(Label.PROPERTIES_SET)); +// assertNotNull(resultSet.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); +// } +// +// @Test +// public void testConnectNodes() { +// // Create both source and destination nodes +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); +// +// // Connect source and destination nodes. +// ResultSet resultSet = client.graphQuery("social", +// "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(b)"); +// +// assertFalse(resultSet.hasNext()); +// assertNull(resultSet.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(resultSet.getStatistics().getStringValue(Label.PROPERTIES_SET)); +// assertEquals(1, resultSet.getStatistics().relationshipsCreated()); +// assertEquals(0, resultSet.getStatistics().relationshipsDeleted()); +// assertNotNull(resultSet.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); +// } +// +// @Test +// public void testDeleteNodes() { +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); +// ResultSet deleteResult = client.graphQuery("social", "MATCH (a:person) WHERE (a.name = 'roi') DELETE a"); +// +// assertFalse(deleteResult.hasNext()); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.PROPERTIES_SET)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.RELATIONSHIPS_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.RELATIONSHIPS_DELETED)); +// assertEquals(1, deleteResult.getStatistics().nodesDeleted()); +// assertNotNull(deleteResult.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); +// +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); +// assertNotNull(client.graphQuery("social", +// "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(a)")); +// deleteResult = client.graphQuery("social", "MATCH (a:person) WHERE (a.name = 'roi') DELETE a"); +// +// assertFalse(deleteResult.hasNext()); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.PROPERTIES_SET)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.RELATIONSHIPS_CREATED)); +// assertEquals(1, deleteResult.getStatistics().relationshipsDeleted()); +// assertEquals(1, deleteResult.getStatistics().nodesDeleted()); +// +// assertNotNull(deleteResult.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); +// } +// +// @Test +// public void testDeleteRelationship() { +// +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); +// assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); +// assertNotNull(client.graphQuery("social", +// "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(a)")); +// ResultSet deleteResult = client.graphQuery("social", "MATCH (a:person)-[e]->() WHERE (a.name = 'roi') DELETE e"); +// +// assertFalse(deleteResult.hasNext()); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.PROPERTIES_SET)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.RELATIONSHIPS_CREATED)); +// assertNull(deleteResult.getStatistics().getStringValue(Label.NODES_DELETED)); +// assertEquals(1, deleteResult.getStatistics().relationshipsDeleted()); +// +// assertNotNull(deleteResult.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); +// } + + @Test + public void testIndex() { + // Create both source and destination nodes + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); + + ResultSet createIndexResult = client.graphQuery("social", "CREATE INDEX ON :person(age)"); + assertFalse(createIndexResult.iterator().hasNext()); + assertEquals(1, createIndexResult.getStatistics().indicesCreated()); + + // since RediSearch as index, those action are allowed + ResultSet createNonExistingIndexResult = client.graphQuery("social", "CREATE INDEX ON :person(age1)"); + assertFalse(createNonExistingIndexResult.iterator().hasNext()); + assertEquals(1, createNonExistingIndexResult.getStatistics().indicesCreated()); + + ResultSet createExistingIndexResult = client.graphQuery("social", "CREATE INDEX ON :person(age)"); + assertFalse(createExistingIndexResult.iterator().hasNext()); + assertEquals(0, createExistingIndexResult.getStatistics().indicesCreated()); + + ResultSet deleteExistingIndexResult = client.graphQuery("social", "DROP INDEX ON :person(age)"); + assertFalse(deleteExistingIndexResult.iterator().hasNext()); + assertEquals(1, deleteExistingIndexResult.getStatistics().indicesDeleted()); + } + + @Test + public void testHeader() { + + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); + assertNotNull(client.graphQuery("social", + "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(a)")); + + ResultSet queryResult = client.graphQuery("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r, a.age"); + + Header header = queryResult.getHeader(); + assertNotNull(header); + assertEquals("HeaderImpl{" +// + "schemaTypes=[COLUMN_SCALAR, COLUMN_SCALAR, COLUMN_SCALAR], " + + "schemaTypes=[SCALAR, SCALAR, SCALAR], " + + "schemaNames=[a, r, a.age]}", header.toString()); + // Assert.assertEquals(-1901778507, header.hashCode()); + + List schemaNames = header.getSchemaNames(); + + assertNotNull(schemaNames); + assertEquals(3, schemaNames.size()); + assertEquals("a", schemaNames.get(0)); + assertEquals("r", schemaNames.get(1)); + assertEquals("a.age", schemaNames.get(2)); + } + + @Test + public void testRecord() { + String name = "roi"; + int age = 32; + double doubleValue = 3.14; + boolean boolValue = true; + + String place = "TLV"; + int since = 2000; + + Property nameProperty = new Property<>("name", name); + Property ageProperty = new Property<>("age", age); + Property doubleProperty = new Property<>("doubleValue", doubleValue); + Property trueBooleanProperty = new Property<>("boolValue", true); + Property falseBooleanProperty = new Property<>("boolValue", false); + + Property placeProperty = new Property<>("place", place); + Property sinceProperty = new Property<>("since", since); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("person"); + expectedNode.addProperty(nameProperty); + expectedNode.addProperty(ageProperty); + expectedNode.addProperty(doubleProperty); + expectedNode.addProperty(trueBooleanProperty); + assertEquals( + "Node{labels=[person], id=0, " + + "propertyMap={name=Property{name='name', value=roi}, " + + "boolValue=Property{name='boolValue', value=true}, " + + "doubleValue=Property{name='doubleValue', value=3.14}, " + + "age=Property{name='age', value=32}}}", + expectedNode.toString()); + + Edge expectedEdge = new Edge(); + expectedEdge.setId(0); + expectedEdge.setSource(0); + expectedEdge.setDestination(1); + expectedEdge.setRelationshipType("knows"); + expectedEdge.addProperty(placeProperty); + expectedEdge.addProperty(sinceProperty); + expectedEdge.addProperty(doubleProperty); + expectedEdge.addProperty(falseBooleanProperty); + assertEquals("Edge{relationshipType='knows', source=0, destination=1, id=0, " + + "propertyMap={boolValue=Property{name='boolValue', value=false}, " + + "place=Property{name='place', value=TLV}, " + + "doubleValue=Property{name='doubleValue', value=3.14}, " + + "since=Property{name='since', value=2000}}}", expectedEdge.toString()); + + Map params = new HashMap<>(); + params.put("name", name); + params.put("age", age); + params.put("boolValue", boolValue); + params.put("doubleValue", doubleValue); + + assertNotNull(client.graphQuery("social", + "CREATE (:person{name:$name,age:$age, doubleValue:$doubleValue, boolValue:$boolValue})", params)); + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); + assertNotNull( + client.graphQuery("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)")); + + ResultSet resultSet = client.graphQuery("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"); + assertNotNull(resultSet); + + assertEquals(0, resultSet.getStatistics().nodesCreated()); + assertEquals(0, resultSet.getStatistics().nodesDeleted()); + assertEquals(0, resultSet.getStatistics().labelsAdded()); + assertEquals(0, resultSet.getStatistics().propertiesSet()); + assertEquals(0, resultSet.getStatistics().relationshipsCreated()); + assertEquals(0, resultSet.getStatistics().relationshipsDeleted()); + assertNotNull(resultSet.getStatistics().queryIntervalExecutionTime()); + assertFalse(resultSet.getStatistics().queryIntervalExecutionTime().isEmpty()); + + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + + Node node = record.getValue(0); + assertNotNull(node); + + assertEquals(expectedNode, node); + + node = record.getValue("a"); + assertEquals(expectedNode, node); + + Edge edge = record.getValue(1); + assertNotNull(edge); + assertEquals(expectedEdge, edge); + + edge = record.getValue("r"); + assertEquals(expectedEdge, edge); + + assertEquals(Arrays.asList("a", "r", "a.name", "a.age", "a.doubleValue", "a.boolValue", + "r.place", "r.since", "r.doubleValue", "r.boolValue"), record.keys()); + + assertEquals(Arrays.asList(expectedNode, expectedEdge, + name, (long) age, doubleValue, true, + place, (long) since, doubleValue, false), + record.values()); + + Node a = record.getValue("a"); + for (String propertyName : expectedNode.getEntityPropertyNames()) { + assertEquals(expectedNode.getProperty(propertyName), a.getProperty(propertyName)); + } + + assertEquals("roi", record.getString(2)); + assertEquals("32", record.getString(3)); + assertEquals(32L, ((Long) record.getValue(3)).longValue()); + assertEquals(32L, ((Long) record.getValue("a.age")).longValue()); + assertEquals("roi", record.getString("a.name")); + assertEquals("32", record.getString("a.age")); + + } + + @Test + public void testAdditionToProcedures() { + + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'roi',age:32})")); + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'amit',age:30})")); + assertNotNull(client.graphQuery("social", + "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') CREATE (a)-[:knows]->(b)")); + + // expected objects init + Property nameProperty = new Property<>("name", "roi"); + Property ageProperty = new Property<>("age", 32); + Property lastNameProperty = new Property<>("lastName", "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("person"); + expectedNode.addProperty(nameProperty); + expectedNode.addProperty(ageProperty); + + Edge expectedEdge = new Edge(); + expectedEdge.setId(0); + expectedEdge.setSource(0); + expectedEdge.setDestination(1); + expectedEdge.setRelationshipType("knows"); + + ResultSet resultSet = client.graphQuery("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r"); + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(2, schemaNames.size()); + assertEquals("a", schemaNames.get(0)); + assertEquals("r", schemaNames.get(1)); + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("a", "r"), record.keys()); + assertEquals(Arrays.asList(expectedNode, expectedEdge), record.values()); + + // test for local cache updates + + expectedNode.removeProperty("name"); + expectedNode.removeProperty("age"); + expectedNode.addProperty(lastNameProperty); + expectedNode.removeLabel("person"); + expectedNode.addLabel("worker"); + expectedNode.setId(2); + expectedEdge.setRelationshipType("worksWith"); + expectedEdge.setSource(2); + expectedEdge.setDestination(3); + expectedEdge.setId(1); + assertNotNull(client.graphQuery("social", "CREATE (:worker{lastName:'a'})")); + assertNotNull(client.graphQuery("social", "CREATE (:worker{lastName:'b'})")); + assertNotNull(client.graphQuery("social", + "MATCH (a:worker), (b:worker) WHERE (a.lastName = 'a' AND b.lastName='b') CREATE (a)-[:worksWith]->(b)")); + resultSet = client.graphQuery("social", "MATCH (a:worker)-[r:worksWith]->(b:worker) RETURN a,r"); + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(2, schemaNames.size()); + assertEquals("a", schemaNames.get(0)); + assertEquals("r", schemaNames.get(1)); + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("a", "r"), record.keys()); + assertEquals(Arrays.asList(expectedNode, expectedEdge), record.values()); + } + + @Test + public void testEscapedQuery() { + Map params1 = new HashMap(); + params1.put("s1", "S\"'"); + params1.put("s2", "S'\""); + assertNotNull(client.graphQuery("social", "CREATE (:escaped{s1:$s1,s2:$s2})", params1)); + + Map params2 = new HashMap(); + params2.put("s1", "S\"'"); + params2.put("s2", "S'\""); + assertNotNull(client.graphQuery("social", "MATCH (n) where n.s1=$s1 and n.s2=$s2 RETURN n", params2)); + + assertNotNull(client.graphQuery("social", "MATCH (n) where n.s1='S\"' RETURN n")); + } + + @Test + public void testArraySupport() { + + Node expectedANode = new Node(); + expectedANode.setId(0); + expectedANode.addLabel("person"); + Property aNameProperty = new Property<>("name", "a"); + Property aAgeProperty = new Property<>("age", 32); + Property> aListProperty = new Property<>("array", Arrays.asList(0L, 1L, 2L)); + expectedANode.addProperty(aNameProperty); + expectedANode.addProperty(aAgeProperty); + expectedANode.addProperty(aListProperty); + + Node expectedBNode = new Node(); + expectedBNode.setId(1); + expectedBNode.addLabel("person"); + Property bNameProperty = new Property<>("name", "b"); + Property bAgeProperty = new Property<>("age", 30); + Property> bListProperty = new Property<>("array", Arrays.asList(3L, 4L, 5L)); + expectedBNode.addProperty(bNameProperty); + expectedBNode.addProperty(bAgeProperty); + expectedBNode.addProperty(bListProperty); + + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'a',age:32,array:[0,1,2]})")); + assertNotNull(client.graphQuery("social", "CREATE (:person{name:'b',age:30,array:[3,4,5]})")); + + // test array + + ResultSet resultSet = client.graphQuery("social", "WITH [0,1,2] as x return x"); + + // check header + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("x", schemaNames.get(0)); + + // check record + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("x"), record.keys()); + + List x = record.getValue("x"); + assertEquals(Arrays.asList(0L, 1L, 2L), x); + + // test collect + resultSet = client.graphQuery("social", "MATCH(n) return collect(n) as x"); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("x", schemaNames.get(0)); + + // check record + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("x"), record.keys()); + x = record.getValue("x"); + assertEquals(Arrays.asList(expectedANode, expectedBNode), x); + + // test unwind + resultSet = client.graphQuery("social", "unwind([0,1,2]) as x return x"); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("x", schemaNames.get(0)); + + // check record + assertEquals(3, resultSet.size()); + iterator = resultSet.iterator(); + for (long i = 0; i < 3; i++) { + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertEquals(Arrays.asList("x"), record.keys()); + assertEquals(i, (long) record.getValue("x")); + } + } + + @Test + public void testPath() { + List nodes = new ArrayList<>(3); + for (int i = 0; i < 3; i++) { + Node node = new Node(); + node.setId(i); + node.addLabel("L1"); + nodes.add(node); + } + + List edges = new ArrayList<>(2); + for (int i = 0; i < 2; i++) { + Edge edge = new Edge(); + edge.setId(i); + edge.setRelationshipType("R1"); + edge.setSource(i); + edge.setDestination(i + 1); + edges.add(edge); + } + + Set expectedPaths = new HashSet<>(); + + Path path01 = new PathBuilder(2).append(nodes.get(0)).append(edges.get(0)).append(nodes.get(1)).build(); + Path path12 = new PathBuilder(2).append(nodes.get(1)).append(edges.get(1)).append(nodes.get(2)).build(); + Path path02 = new PathBuilder(3).append(nodes.get(0)).append(edges.get(0)).append(nodes.get(1)) + .append(edges.get(1)).append(nodes.get(2)).build(); + + expectedPaths.add(path01); + expectedPaths.add(path12); + expectedPaths.add(path02); + + client.graphQuery("social", "CREATE (:L1)-[:R1]->(:L1)-[:R1]->(:L1)"); + + ResultSet resultSet = client.graphQuery("social", "MATCH p = (:L1)-[:R1*]->(:L1) RETURN p"); + + assertEquals(expectedPaths.size(), resultSet.size()); + Iterator iterator = resultSet.iterator(); + for (int i = 0; i < resultSet.size(); i++) { + Path p = iterator.next().getValue("p"); + assertTrue(expectedPaths.contains(p)); + expectedPaths.remove(p); + } + + } + + @Test + public void testParameters() { + Object[] parameters = { 1, 2.3, true, false, null, "str", 'a', "b", Arrays.asList(1, 2, 3), + new Integer[] { 1, 2, 3 } }; + Object[] expected_anwsers = { 1L, 2.3, true, false, null, "str", "a", "b", Arrays.asList(1L, 2L, 3L), + new Long[] { 1L, 2L, 3L } }; + Map params = new HashMap<>(); + for (int i = 0; i < parameters.length; i++) { + Object param = parameters[i]; + params.put("param", param); + ResultSet resultSet = client.graphQuery("social", "RETURN $param", params); + assertEquals(1, resultSet.size()); + Record r = resultSet.iterator().next(); + Object o = r.getValue(0); + Object expected = expected_anwsers[i]; + if (i == parameters.length - 1) { + expected = Arrays.asList((Object[]) expected); + } + assertEquals(expected, o); + } + } + + @Test + public void testParametersReadOnly() { + Object[] parameters = { 1, 2.3, true, false, null, "str", 'a', "b", Arrays.asList(1, 2, 3), + new Integer[] { 1, 2, 3 } }; + Object[] expected_anwsers = { 1L, 2.3, true, false, null, "str", "a", "b", Arrays.asList(1L, 2L, 3L), + new Long[] { 1L, 2L, 3L } }; + Map params = new HashMap<>(); + for (int i = 0; i < parameters.length; i++) { + Object param = parameters[i]; + params.put("param", param); + ResultSet resultSetRo = client.graphReadonlyQuery("social", "RETURN $param", params); + assertEquals(1, resultSetRo.size()); + Record rRo = resultSetRo.iterator().next(); + Object oRo = rRo.getValue(0); + Object expected = expected_anwsers[i]; + if (i == parameters.length - 1) { + expected = Arrays.asList((Object[]) expected); + } + assertEquals(expected, oRo); + } + } + + @Test + public void testNullGraphEntities() { + // Create two nodes connected by a single outgoing edge. + assertNotNull(client.graphQuery("social", "CREATE (:L)-[:E]->(:L2)")); + // Test a query that produces 1 record with 3 null values. + ResultSet resultSet = client.graphQuery("social", "OPTIONAL MATCH (a:NONEXISTENT)-[e]->(b) RETURN a, e, b"); + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList(null, null, null), record.values()); + + // Test a query that produces 2 records, with 2 null values in the second. + resultSet = client.graphQuery("social", "MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, e, b ORDER BY ID(a)"); + assertEquals(2, resultSet.size()); + iterator = resultSet.iterator(); + record = iterator.next(); + assertEquals(3, record.size()); + + assertNotNull(record.getValue(0)); + assertNotNull(record.getValue(1)); + assertNotNull(record.getValue(2)); + + record = iterator.next(); + assertEquals(3, record.size()); + + assertNotNull(record.getValue(0)); + assertNull(record.getValue(1)); + assertNull(record.getValue(2)); + + // Test a query that produces 2 records, the first containing a path and the + // second containing a null value. + resultSet = client.graphQuery("social", "MATCH (a) OPTIONAL MATCH p = (a)-[e]->(b) RETURN p"); + assertEquals(2, resultSet.size()); + iterator = resultSet.iterator(); + + record = iterator.next(); + assertEquals(1, record.size()); + assertNotNull(record.getValue(0)); + + record = iterator.next(); + assertEquals(1, record.size()); + assertNull(record.getValue(0)); + } + + @Test + public void test64bitnumber() { + long value = 1L << 40; + Map params = new HashMap<>(); + params.put("val", value); + ResultSet resultSet = client.graphQuery("social", "CREATE (n {val:$val}) RETURN n.val", params); + assertEquals(1, resultSet.size()); + Record r = resultSet.iterator().next(); + assertEquals(Long.valueOf(value), r.getValue(0)); + } + + @Test + public void testCachedExecution() { + client.graphQuery("social", "CREATE (:N {val:1}), (:N {val:2})"); + + // First time should not be loaded from execution cache + Map params = new HashMap<>(); + params.put("val", 1L); + ResultSet resultSet = client.graphQuery("social", "MATCH (n:N {val:$val}) RETURN n.val", params); + assertEquals(1, resultSet.size()); + Record r = resultSet.iterator().next(); + assertEquals(params.get("val"), r.getValue(0)); + assertFalse(resultSet.getStatistics().cachedExecution()); + + // Run in loop many times to make sure the query will be loaded + // from cache at least once + for (int i = 0; i < 64; i++) { + resultSet = client.graphQuery("social", "MATCH (n:N {val:$val}) RETURN n.val", params); + } + assertEquals(1, resultSet.size()); + r = resultSet.iterator().next(); + assertEquals(params.get("val"), r.getValue(0)); + assertTrue(resultSet.getStatistics().cachedExecution()); + } + + @Test + public void testMapDataType() { + Map expected = new HashMap<>(); + expected.put("a", (long) 1); + expected.put("b", "str"); + expected.put("c", null); + List d = new ArrayList<>(); + d.add((long) 1); + d.add((long) 2); + d.add((long) 3); + expected.put("d", d); + expected.put("e", true); + Map f = new HashMap<>(); + f.put("x", (long) 1); + f.put("y", (long) 2); + expected.put("f", f); + ResultSet res = client.graphQuery("social", "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}"); + assertEquals(1, res.size()); + Record r = res.iterator().next(); + Map actual = r.getValue(0); + assertEquals(expected, actual); + } + + @Test + public void testGeoPointLatLon() { + ResultSet rs = client.graphQuery("social", "CREATE (:restaurant" + + " {location: point({latitude:30.27822306, longitude:-97.75134723})})"); + assertEquals(1, rs.getStatistics().nodesCreated()); + assertEquals(1, rs.getStatistics().propertiesSet()); + + assertTestGeoPoint(); + } + + @Test + public void testGeoPointLonLat() { + ResultSet rs = client.graphQuery("social", "CREATE (:restaurant" + + " {location: point({longitude:-97.75134723, latitude:30.27822306})})"); + assertEquals(1, rs.getStatistics().nodesCreated()); + assertEquals(1, rs.getStatistics().propertiesSet()); + + assertTestGeoPoint(); + } + + private void assertTestGeoPoint() { + ResultSet results = client.graphQuery("social", "MATCH (restaurant) RETURN restaurant"); + assertEquals(1, results.size()); + Record record = results.iterator().next(); + assertEquals(1, record.size()); + assertEquals(Collections.singletonList("restaurant"), record.keys()); + Node node = record.getValue(0); + Property property = node.getProperty("location"); + assertEquals(new Point(30.27822306, -97.75134723), property.getValue()); + } + + @Test + public void timeoutArgument() { + ResultSet rs = client.graphQuery("social", "UNWIND range(0,100) AS x WITH x AS x WHERE x = 100 RETURN x", 1L); + assertEquals(1, rs.size()); + Record r = rs.iterator().next(); + assertEquals(Long.valueOf(100), r.getValue(0)); + } + + @Test + public void testCachedExecutionReadOnly() { + client.graphQuery("social", "CREATE (:N {val:1}), (:N {val:2})"); + + // First time should not be loaded from execution cache + Map params = new HashMap<>(); + params.put("val", 1L); + ResultSet resultSet = client.graphReadonlyQuery("social", "MATCH (n:N {val:$val}) RETURN n.val", params); + assertEquals(1, resultSet.size()); + Record r = resultSet.iterator().next(); + assertEquals(params.get("val"), r.getValue(0)); + assertFalse(resultSet.getStatistics().cachedExecution()); + + // Run in loop many times to make sure the query will be loaded + // from cache at least once + for (int i = 0; i < 64; i++) { + resultSet = client.graphReadonlyQuery("social", "MATCH (n:N {val:$val}) RETURN n.val", params); + } + assertEquals(1, resultSet.size()); + r = resultSet.iterator().next(); + assertEquals(params.get("val"), r.getValue(0)); + assertTrue(resultSet.getStatistics().cachedExecution()); + } + + @Test + public void testSimpleReadOnly() { + client.graphQuery("social", "CREATE (:person{name:'filipe',age:30})"); + ResultSet rsRo = client.graphReadonlyQuery("social", "MATCH (a:person) WHERE (a.name = 'filipe') RETURN a.age"); + assertEquals(1, rsRo.size()); + Record r = rsRo.iterator().next(); + assertEquals(Long.valueOf(30), r.getValue(0)); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java new file mode 100644 index 0000000000..2ca5dc43f9 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java @@ -0,0 +1,226 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.graph.Header; +import redis.clients.jedis.graph.Record; +import redis.clients.jedis.graph.ResultSet; +import redis.clients.jedis.graph.entities.Node; +import redis.clients.jedis.graph.entities.Property; +import redis.clients.jedis.modules.RedisModuleCommandsTestBase; + +public class GraphPipelineTest extends RedisModuleCommandsTestBase { + + private Connection c; + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } +// +// @Before +// public void createApi() { +// api = new RedisGraph(); +// } +// +// @After +// public void deleteGraph() { +// api.deleteGraph("social"); +// api.close(); +// } + + @Before + public void createApi() { + c = createConnection(); + } + + @After + public void deleteGraph() { + c.close(); + } + + @Test + public void testSync() { + Pipeline pipeline = new Pipeline(c); + + pipeline.set("x", "1"); + pipeline.graphQuery("social", "CREATE (:Person {name:'a'})"); + pipeline.graphQuery("g", "CREATE (:Person {name:'a'})"); + pipeline.incr("x"); + pipeline.get("x"); + pipeline.graphQuery("social", "MATCH (n:Person) RETURN n"); + pipeline.graphDelete("g"); +// pipeline.callProcedure("social", "db.labels"); + pipeline.graphQuery("social", "CALL db.labels()"); + List results = pipeline.syncAndReturnAll(); + + // Redis set command + assertEquals(String.class, results.get(0).getClass()); + assertEquals("OK", results.get(0)); + + // Redis graph command +// assertEquals(ResultSetImpl.class, results.get(1).getClass()); + ResultSet resultSet = (ResultSet) results.get(1); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + +// assertEquals(ResultSetImpl.class, results.get(2).getClass()); + resultSet = (ResultSet) results.get(2); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + + // Redis incr command + assertEquals(Long.class, results.get(3).getClass()); + assertEquals(2L, results.get(3)); + + // Redis get command + assertEquals(String.class, results.get(4).getClass()); + assertEquals("2", results.get(4)); + + // Graph query result +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(5); + + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("n", schemaNames.get(0)); + + Property nameProperty = new Property<>("name", "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("Person"); + expectedNode.addProperty(nameProperty); + // see that the result were pulled from the right graph + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("n"), record.keys()); + assertEquals(expectedNode, record.getValue("n")); + +// assertEquals(ResultSetImpl.class, results.get(7).getClass()); + resultSet = (ResultSet) results.get(7); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("label", schemaNames.get(0)); + + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("label"), record.keys()); + assertEquals("Person", record.getValue("label")); + } + + @Test + public void testReadOnlyQueries() { + Pipeline pipeline = new Pipeline(c); + + pipeline.set("x", "1"); + pipeline.graphQuery("social", "CREATE (:Person {name:'a'})"); + pipeline.graphQuery("g", "CREATE (:Person {name:'a'})"); + pipeline.graphReadonlyQuery("social", "MATCH (n:Person) RETURN n"); + pipeline.graphDelete("g"); +// pipeline.callProcedure("social", "db.labels"); + pipeline.graphQuery("social", "CALL db.labels()"); + List results = pipeline.syncAndReturnAll(); + + // Redis set command + assertEquals(String.class, results.get(0).getClass()); + assertEquals("OK", results.get(0)); + + // Redis graph command +// assertEquals(ResultSetImpl.class, results.get(1).getClass()); + ResultSet resultSet = (ResultSet) results.get(1); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + +// assertEquals(ResultSetImpl.class, results.get(2).getClass()); + resultSet = (ResultSet) results.get(2); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + + // Graph read-only query result +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(3); + + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("n", schemaNames.get(0)); + + Property nameProperty = new Property<>("name", "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("Person"); + expectedNode.addProperty(nameProperty); + // see that the result were pulled from the right graph + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("n"), record.keys()); + assertEquals(expectedNode, record.getValue("n")); + +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(5); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("label", schemaNames.get(0)); + + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("label"), record.keys()); + assertEquals("Person", record.getValue("label")); + } + + @Test + public void testWaitReplicas() { + Pipeline pipeline = new Pipeline(c); + pipeline.set("x", "1"); + pipeline.graphQuery("social", "CREATE (:Person {name:'a'})"); + pipeline.graphQuery("g", "CREATE (:Person {name:'a'})"); + pipeline.waitReplicas(0, 100L); + List results = pipeline.syncAndReturnAll(); + assertEquals(Long.valueOf(0), results.get(3)); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java new file mode 100644 index 0000000000..26ee4f135f --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java @@ -0,0 +1,253 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.Transaction; +import redis.clients.jedis.graph.Header; +import redis.clients.jedis.graph.Record; +import redis.clients.jedis.graph.ResultSet; +import redis.clients.jedis.graph.entities.Node; +import redis.clients.jedis.graph.entities.Property; +import redis.clients.jedis.modules.RedisModuleCommandsTestBase; + +public class GraphTransactionTest extends RedisModuleCommandsTestBase { + + private Connection c; + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } +// +// @Before +// public void createApi() { +// api = new RedisGraph(); +// } +// +// @After +// public void deleteGraph() { +// api.deleteGraph("social"); +// api.close(); +// } + + @Before + public void createApi() { + c = createConnection(); + } + + @After + public void deleteGraph() { + c.close(); + } + + @Test + public void testMultiExec() { + Transaction transaction = new Transaction(c); + + transaction.set("x", "1"); + transaction.graphQuery("social", "CREATE (:Person {name:'a'})"); + transaction.graphQuery("g", "CREATE (:Person {name:'a'})"); + transaction.incr("x"); + transaction.get("x"); + transaction.graphQuery("social", "MATCH (n:Person) RETURN n"); + transaction.graphDelete("g"); +// transaction.callProcedure("social", "db.labels"); + transaction.graphQuery("social", "CALL db.labels()"); + List results = transaction.exec(); + + // Redis set command + assertEquals(String.class, results.get(0).getClass()); + assertEquals("OK", results.get(0)); + + // Redis graph command +// assertEquals(ResultSetImpl.class, results.get(1).getClass()); + ResultSet resultSet = (ResultSet) results.get(1); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + +// assertEquals(ResultSetImpl.class, results.get(2).getClass()); + resultSet = (ResultSet) results.get(2); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + + // Redis incr command + assertEquals(Long.class, results.get(3).getClass()); + assertEquals(2L, results.get(3)); + + // Redis get command + assertEquals(String.class, results.get(4).getClass()); + assertEquals("2", results.get(4)); + + // Graph query result +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(5); + + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("n", schemaNames.get(0)); + + Property nameProperty = new Property<>("name", "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("Person"); + expectedNode.addProperty(nameProperty); + // see that the result were pulled from the right graph + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("n"), record.keys()); + assertEquals(expectedNode, record.getValue("n")); + +// assertEquals(ResultSetImpl.class, results.get(7).getClass()); + resultSet = (ResultSet) results.get(7); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("label", schemaNames.get(0)); + + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("label"), record.keys()); + assertEquals("Person", record.getValue("label")); + } +// +// @Test +// public void testWriteTransactionWatch() { +// +// RedisGraphContext c1 = api.getContext(); +// RedisGraphContext c2 = api.getContext(); +// +// c1.watch("social"); +// RedisGraphTransaction t1 = c1.multi(); +// +// t1.graphQuery("social", "CREATE (:Person {name:'a'})"); +// c2.graphQuery("social", "CREATE (:Person {name:'b'})"); +// List returnValue = t1.exec(); +// assertNull(returnValue); +// c1.close(); +// c2.close(); +// } +// +// @Test +// public void testReadTransactionWatch() { +// +// RedisGraphContext c1 = api.getContext(); +// RedisGraphContext c2 = api.getContext(); +// assertNotEquals(c1.getConnectionContext(), c2.getConnectionContext()); +// c1.graphQuery("social", "CREATE (:Person {name:'a'})"); +// c1.watch("social"); +// RedisGraphTransaction t1 = c1.multi(); +// +// Map params = new HashMap<>(); +// params.put("name", 'b'); +// t1.graphQuery("social", "CREATE (:Person {name:$name})", params); +// c2.graphQuery("social", "MATCH (n) return n"); +// List returnValue = t1.exec(); +// +// assertNotNull(returnValue); +// c1.close(); +// c2.close(); +// } + + @Test + public void testMultiExecWithReadOnlyQueries() { + Transaction transaction = new Transaction(c); + + transaction.set("x", "1"); + transaction.graphQuery("social", "CREATE (:Person {name:'a'})"); + transaction.graphQuery("g", "CREATE (:Person {name:'a'})"); + transaction.graphReadonlyQuery("social", "MATCH (n:Person) RETURN n"); + transaction.graphDelete("g"); +// transaction.callProcedure("social", "db.labels"); + transaction.graphQuery("social", "CALL db.labels()"); + List results = transaction.exec(); + + // Redis set command + assertEquals(String.class, results.get(0).getClass()); + assertEquals("OK", results.get(0)); + + // Redis graph command +// assertEquals(ResultSetImpl.class, results.get(1).getClass()); + ResultSet resultSet = (ResultSet) results.get(1); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + +// assertEquals(ResultSetImpl.class, results.get(2).getClass()); + resultSet = (ResultSet) results.get(2); + assertEquals(1, resultSet.getStatistics().nodesCreated()); + assertEquals(1, resultSet.getStatistics().propertiesSet()); + + // Graph read-only query result +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(3); + + assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + List schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("n", schemaNames.get(0)); + + Property nameProperty = new Property<>("name", "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("Person"); + expectedNode.addProperty(nameProperty); + // see that the result were pulled from the right graph + assertEquals(1, resultSet.size()); + Iterator iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + Record record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("n"), record.keys()); + assertEquals(expectedNode, record.getValue("n")); + +// assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(5); + + assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + schemaNames = header.getSchemaNames(); + assertNotNull(schemaNames); + assertEquals(1, schemaNames.size()); + assertEquals("label", schemaNames.get(0)); + + assertEquals(1, resultSet.size()); + iterator = resultSet.iterator(); + assertTrue(iterator.hasNext()); + record = iterator.next(); + assertFalse(iterator.hasNext()); + assertEquals(Arrays.asList("label"), record.keys()); + assertEquals("Person", record.getValue("label")); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/PathBuilder.java b/src/test/java/redis/clients/jedis/modules/graph/PathBuilder.java new file mode 100644 index 0000000000..27f02df132 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/PathBuilder.java @@ -0,0 +1,58 @@ +package redis.clients.jedis.modules.graph; + +import java.util.ArrayList; +import java.util.List; + +import redis.clients.jedis.graph.entities.Edge; +import redis.clients.jedis.graph.entities.Node; +import redis.clients.jedis.graph.entities.Path; + +public final class PathBuilder { + + private final List nodes; + private final List edges; + private Class currentAppendClass; + + public PathBuilder() { + this.nodes = new ArrayList<>(0); + this.edges = new ArrayList<>(0); + currentAppendClass = Node.class; + } + + public PathBuilder(int nodesCount) { + nodes = new ArrayList<>(nodesCount); + edges = new ArrayList<>(nodesCount - 1 >= 0 ? nodesCount - 1 : 0); + currentAppendClass = Node.class; + } + + public PathBuilder append(Object object) { + Class c = object.getClass(); + if (!currentAppendClass.equals(c)) { + throw new IllegalArgumentException("Path Builder expected " + currentAppendClass.getSimpleName() + " but was " + c.getSimpleName()); + } + if (c.equals(Node.class)) { + return appendNode((Node) object); + } else { + return appendEdge((Edge) object); + } + } + + private PathBuilder appendEdge(Edge edge) { + edges.add(edge); + currentAppendClass = Node.class; + return this; + } + + private PathBuilder appendNode(Node node) { + nodes.add(node); + currentAppendClass = Edge.class; + return this; + } + + public Path build() { + if (nodes.size() != edges.size() + 1) { + throw new IllegalArgumentException("Path builder nodes count should be edge count + 1"); + } + return new Path(nodes, edges); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java new file mode 100644 index 0000000000..95011cc7bc --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java @@ -0,0 +1,28 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import redis.clients.jedis.graph.entities.Edge; + +public class PathBuilderTest { + + @Test + public void testPathBuilderSizeException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + PathBuilder builder = new PathBuilder(0); + builder.build(); + }); + assertTrue(exception.getMessage().equalsIgnoreCase("Path builder nodes count should be edge count + 1")); + } + + @Test + public void testPathBuilderArgumentsException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + PathBuilder builder = new PathBuilder(0); + builder.append(new Edge()); + }); + assertTrue(exception.getMessage().equalsIgnoreCase("Path Builder expected Node but was Edge")); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/PathTest.java b/src/test/java/redis/clients/jedis/modules/graph/PathTest.java new file mode 100644 index 0000000000..3bf2823d76 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/PathTest.java @@ -0,0 +1,74 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import redis.clients.jedis.graph.entities.Edge; +import redis.clients.jedis.graph.entities.Node; +import redis.clients.jedis.graph.entities.Path; + +public class PathTest { + + private Node buildNode(int id) { + Node n = new Node(); + n.setId(0); + return n; + } + + private Edge buildEdge(int id, int src, int dst) { + Edge e = new Edge(); + e.setId(id); + e.setSource(src); + e.setDestination(dst); + return e; + } + + private List buildNodeArray(int size) { + return IntStream.range(0, size).mapToObj(i -> buildNode(i)).collect(Collectors.toList()); + } + + private List buildEdgeArray(int size) { + return IntStream.range(0, size).mapToObj(i -> buildEdge(i, i, i + 1)).collect(Collectors.toList()); + } + + private Path buildPath(int nodeCount) { + return new Path(buildNodeArray(nodeCount), buildEdgeArray(nodeCount - 1)); + } + + @Test + public void testEmptyPath() { + Path path = buildPath(0); + assertEquals(0, path.length()); + assertEquals(0, path.nodeCount()); + assertThrows(IndexOutOfBoundsException.class, () -> path.getNode(0)); + assertThrows(IndexOutOfBoundsException.class, () -> path.getEdge(0)); + } + + @Test + public void testSingleNodePath() { + Path path = buildPath(1); + assertEquals(0, path.length()); + assertEquals(1, path.nodeCount()); + Node n = new Node(); + n.setId(0); + assertEquals(n, path.firstNode()); + assertEquals(n, path.lastNode()); + assertEquals(n, path.getNode(0)); + } + + @Test + public void testRandomLengthPath() { + int nodeCount = ThreadLocalRandom.current().nextInt(2, 100 + 1); + Path path = buildPath(nodeCount); + assertEquals(buildNodeArray(nodeCount), path.getNodes()); + assertEquals(buildEdgeArray(nodeCount - 1), path.getEdges()); + path.getEdge(0); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/graph/UtilsTest.java b/src/test/java/redis/clients/jedis/modules/graph/UtilsTest.java new file mode 100644 index 0000000000..f4e775c2f5 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/graph/UtilsTest.java @@ -0,0 +1,64 @@ +package redis.clients.jedis.modules.graph; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import redis.clients.jedis.graph.RedisGraphQueryUtil; + +public class UtilsTest { +// +// @Test +// public void testPrepareProcedure() { +// assertEquals("CALL prc()", RedisGraphQueryUtil.prepareProcedure("prc", Arrays.asList(new String[]{}), new HashMap<>())); +// +// assertEquals("CALL prc(\"a\",\"b\")", RedisGraphQueryUtil.prepareProcedure("prc", Arrays.asList(new String[]{"a", "b"}), new HashMap<>())); +// +// Map> kwargs = new HashMap<>(); +// kwargs.put("y", Arrays.asList(new String[]{"ka", "kb"})); +// assertEquals("CALL prc(\"a\",\"b\")ka,kb", RedisGraphQueryUtil.prepareProcedure("prc", Arrays.asList(new String[]{"a", "b"}), kwargs)); +// +// assertEquals("CALL prc()ka,kb", RedisGraphQueryUtil.prepareProcedure("prc", Arrays.asList(new String[]{}), kwargs)); +// } + + @Test + public void testParamsPrep() { + Map params = new HashMap<>(); + params.put("param", ""); + assertEquals("CYPHER param=\"\" RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", "\""); + assertEquals("CYPHER param=\"\\\"\" RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", "\"st"); + assertEquals("CYPHER param=\"\\\"st\" RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", 1); + assertEquals("CYPHER param=1 RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", 2.3); + assertEquals("CYPHER param=2.3 RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", true); + assertEquals("CYPHER param=true RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", false); + assertEquals("CYPHER param=false RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", null); + assertEquals("CYPHER param=null RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", "str"); + assertEquals("CYPHER param=\"str\" RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + params.put("param", "s\"tr"); + assertEquals("CYPHER param=\"s\\\"tr\" RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + Integer arr[] = {1, 2, 3}; + params.put("param", arr); + assertEquals("CYPHER param=[1, 2, 3] RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + List list = Arrays.asList(1, 2, 3); + params.put("param", list); + assertEquals("CYPHER param=[1, 2, 3] RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + String strArr[] = {"1", "2", "3"}; + params.put("param", strArr); + assertEquals("CYPHER param=[\"1\", \"2\", \"3\"] RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + List stringList = Arrays.asList("1", "2", "3"); + params.put("param", stringList); + assertEquals("CYPHER param=[\"1\", \"2\", \"3\"] RETURN $param", RedisGraphQueryUtil.prepareQuery("RETURN $param", params)); + } + +} diff --git a/src/test/java/redis/clients/jedis/modules/search/AggregationBuilderTest.java b/src/test/java/redis/clients/jedis/modules/search/AggregationBuilderTest.java index d7742be1ba..bea9d324a0 100644 --- a/src/test/java/redis/clients/jedis/modules/search/AggregationBuilderTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/AggregationBuilderTest.java @@ -2,7 +2,6 @@ import redis.clients.jedis.exceptions.JedisDataException; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -30,11 +29,11 @@ public class AggregationBuilderTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } private void addDocument(Document doc) { String key = doc.getId(); diff --git a/src/test/java/redis/clients/jedis/modules/search/JsonSearchTest.java b/src/test/java/redis/clients/jedis/modules/search/JsonSearchTest.java index d4fe65af17..7879aa1704 100644 --- a/src/test/java/redis/clients/jedis/modules/search/JsonSearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/JsonSearchTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.*; import org.json.JSONObject; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import redis.clients.jedis.BuilderFactory; @@ -27,11 +26,11 @@ public class JsonSearchTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } private void setJson(String key, JSONObject json) { CommandObject command = new CommandObject<>( diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java index 13c872eec8..0013e89d29 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java @@ -5,7 +5,6 @@ import java.util.*; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -23,11 +22,11 @@ public class SearchTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } private void addDocument(String key, Map map) { client.hset(key, toStringMap(map)); diff --git a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java index 7fcd55f91e..062a379d32 100644 --- a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java +++ b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java @@ -7,7 +7,6 @@ import static org.junit.Assert.fail; import java.util.*; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import redis.clients.jedis.exceptions.JedisDataException; @@ -20,11 +19,11 @@ public class TimeSeriesTest extends RedisModuleCommandsTestBase { public static void prepare() { RedisModuleCommandsTestBase.prepare(); } - - @AfterClass - public static void tearDown() { -// RedisModuleCommandsTestBase.tearDown(); - } +// +// @AfterClass +// public static void tearDown() { +//// RedisModuleCommandsTestBase.tearDown(); +// } @Test public void testCreate() {