diff --git a/.circleci/config.yml b/.circleci/config.yml
index cfa036b..abebfaf 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,7 +8,7 @@ jobs:
docker:
- image: circleci/openjdk:8u171-jdk
- - image: redislabs/redisgraph:edge
+ - image: redislabs/redisgraph:2.0-edge
port: 6379:6379
working_directory: ~/repo
@@ -44,7 +44,7 @@ jobs:
- run: bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
- - run: mvn -s .circleci.settings.xml -DskipTests deploy
+ - run: mvn -s .circleci.settings.xml -DskipTests deploy
workflows:
version: 2
diff --git a/.gitignore b/.gitignore
index 0740f5e..0dcfd34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@ hs_err_pid*
.classpath
.project
/.settings/
+
+#intelij
+.idea
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 511fd50..1bd6487 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.redislabs
jredisgraph
- 1.0.7-SNAPSHOT
+ 2.0.0-SNAPSHOT
JRedisGraph
Official client for Redis-Graph
diff --git a/src/main/java/com/redislabs/redisgraph/Header.java b/src/main/java/com/redislabs/redisgraph/Header.java
new file mode 100644
index 0000000..43cf8b3
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/Header.java
@@ -0,0 +1,23 @@
+package com.redislabs.redisgraph;
+
+import java.util.List;
+
+/**
+ * Query response header interface. Represents the response schame (column names and types)
+ */
+public interface Header {
+
+
+ public enum ResultSetColumnTypes {
+ COLUMN_UNKNOWN,
+ COLUMN_SCALAR,
+ COLUMN_NODE,
+ COLUMN_RELATION;
+
+ }
+
+
+ List getSchemaNames();
+
+ List getSchemaTypes();
+}
diff --git a/src/main/java/com/redislabs/redisgraph/RedisGraph.java b/src/main/java/com/redislabs/redisgraph/RedisGraph.java
new file mode 100644
index 0000000..5542538
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/RedisGraph.java
@@ -0,0 +1,190 @@
+package com.redislabs.redisgraph;
+
+import com.redislabs.redisgraph.impl.GraphCache;
+import com.redislabs.redisgraph.impl.ResultSetImpl;
+import org.apache.commons.text.translate.AggregateTranslator;
+import org.apache.commons.text.translate.CharSequenceTranslator;
+import org.apache.commons.text.translate.LookupTranslator;
+import redis.clients.jedis.BinaryClient;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.commands.ProtocolCommand;
+import redis.clients.jedis.util.Pool;
+
+import java.io.Closeable;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+
+/**
+ *
+ */
+public class RedisGraph implements Closeable {
+
+
+
+ private final Pool client;
+ private final Map graphCaches = new ConcurrentHashMap<>();
+
+
+
+ private static final CharSequenceTranslator ESCAPE_CHYPER;
+ static {
+ final Map escapeJavaMap = new HashMap<>();
+ escapeJavaMap.put("\'", "\\'");
+ escapeJavaMap.put("\"", "\\\"");
+ ESCAPE_CHYPER = new AggregateTranslator(new LookupTranslator(Collections.unmodifiableMap(escapeJavaMap)));
+ }
+
+ /**
+ * Creates a client running on the local machine
+
+ */
+ public RedisGraph() {
+ this("localhost", 6379);
+ }
+
+ /**
+ * Creates a client running on the specific host/post
+ *
+ * @param host Redis host
+ * @param port Redis port
+ */
+ public RedisGraph(String host, int port) {
+ this( new JedisPool(host, port));
+ }
+
+ /**
+ * Creates a client using provided Jedis pool
+ *
+ * @param jedis bring your own Jedis pool
+ */
+ public RedisGraph( Pool jedis) {
+
+ this.client = jedis;
+ }
+
+ @Override
+ public void close(){
+ this.client.close();
+ }
+
+
+ /**
+ * Execute a Cypher query with arguments
+ *
+ * @param graphId a graph to perform the query on
+ * @param query Cypher query
+ * @param args
+ * @return a result set
+ */
+ public ResultSet query(String graphId, String query, Object ...args) {
+ if(args.length > 0) {
+ for(int i=0; i rawResponse = null;
+ try(Jedis conn = getConnection()){
+ rawResponse= sendCompactCommand(conn, Command.QUERY, graphId, query).getObjectMultiBulkReply();
+ }
+ return new ResultSetImpl(rawResponse, graphCaches.get(graphId));
+
+ }
+
+ /**
+ * Invokes stored procedures without arguments
+ * @param graphId a graph to perform the query on
+ * @param procedure procedure name to invoke
+ * @return result set with the procedure data
+ */
+ public ResultSet callProcedure(String graphId, String procedure ){
+ return callProcedure(graphId, procedure, new ArrayList<>(), new HashMap<>());
+ }
+
+
+ /**
+ * Invokes stored procedure with arguments
+ * @param graphId a graph to perform the query on
+ * @param procedure procedure name to invoke
+ * @param args procedure arguments
+ * @return result set with the procedure data
+ */
+ public ResultSet callProcedure(String graphId, String procedure, List args ){
+ return callProcedure(graphId, procedure, args, new HashMap<>());
+ }
+
+
+ /**
+ * Deletes the entire graph
+ *
+ * @return delete running time statistics
+ */
+ public String deleteGraph(String graphId) {
+ //clear local state
+ graphCaches.remove(graphId);
+ try (Jedis conn = getConnection()) {
+ return sendCommand(conn, Command.DELETE, graphId).getBulkReply();
+ }
+
+ }
+
+
+ /**
+ * Sends command - will be replaced with sendCompactCommand once graph.delete support --compact flag
+ * @param conn - connection
+ * @param provider - command type
+ * @param args - command arguments
+ * @return
+ */
+ private BinaryClient sendCommand(Jedis conn, ProtocolCommand provider, String ...args) {
+ BinaryClient binaryClient = conn.getClient();
+ binaryClient.sendCommand(provider, args);
+ return binaryClient;
+ }
+
+
+ /**
+ * Sends the command with --COMPACT flag
+ * @param conn - connection
+ * @param provider - command type
+ * @param args - command arguments
+ * @return
+ */
+ private BinaryClient sendCompactCommand(Jedis conn, ProtocolCommand provider, String ...args) {
+ String[] t = new String[args.length +1];
+ System.arraycopy(args, 0 , t, 0, args.length);
+ t[args.length]="--COMPACT";
+ return sendCommand(conn, provider, t);
+ }
+
+ private Jedis getConnection() {
+ return this.client.getResource();
+ }
+
+
+ /**
+ * Invoke a stored procedure
+ * @param graphId a graph to perform the query on
+ * @param procedure - procedure to execute
+ * @param args - procedure arguments
+ * @param kwargs - procedure output arguments
+ * @return
+ */
+ public ResultSet callProcedure(String graphId, String procedure, List args , Map> kwargs ){
+
+ args = args.stream().map( s -> Utils.quoteString(s)).collect(Collectors.toList());
+ StringBuilder queryString = new StringBuilder();
+ queryString.append(String.format("CALL %s(%s)", procedure, String.join(",", args)));
+ List kwargsList = kwargs.getOrDefault("y", null);
+ if(kwargsList != null){
+ queryString.append(String.join(",", kwargsList));
+ }
+ return query(graphId, queryString.toString());
+ }
+}
diff --git a/src/main/java/com/redislabs/redisgraph/RedisGraphAPI.java b/src/main/java/com/redislabs/redisgraph/RedisGraphAPI.java
deleted file mode 100644
index 31f7b35..0000000
--- a/src/main/java/com/redislabs/redisgraph/RedisGraphAPI.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.redislabs.redisgraph;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.redislabs.redisgraph.impl.ResultSetImpl;
-import org.apache.commons.text.translate.AggregateTranslator;
-import org.apache.commons.text.translate.CharSequenceTranslator;
-import org.apache.commons.text.translate.LookupTranslator;
-
-import redis.clients.jedis.BinaryClient;
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
-import redis.clients.jedis.commands.ProtocolCommand;
-import redis.clients.jedis.util.Pool;
-
-/**
- * RedisGraph client
- */
-public class RedisGraphAPI {
-
- private final Pool client;
- private final String graphId;
-
- private static final CharSequenceTranslator ESCAPE_CHYPER;
- static {
- final Map escapeJavaMap = new HashMap<>();
- escapeJavaMap.put("\'", "\\'");
- escapeJavaMap.put("\"", "\\\"");
- ESCAPE_CHYPER = new AggregateTranslator(new LookupTranslator(Collections.unmodifiableMap(escapeJavaMap)));
- }
-
- /**
- * Creates a client to a specific graph running on the local machine
- *
- * @param graphId the graph id
- */
- public RedisGraphAPI(String graphId) {
- this(graphId, "localhost", 6379);
- }
-
- /**
- * Creates a client to a specific graph running on the specific host/post
- *
- * @param graphId the graph id
- * @param host Redis host
- * @param port Redis port
- */
- public RedisGraphAPI(String graphId, String host, int port) {
- this(graphId, new JedisPool(host, port));
- }
-
- /**
- * Creates a client to a specific graph using provided Jedis pool
- *
- * @param graphId the graph id
- * @param jedis bring your own Jedis pool
- */
- public RedisGraphAPI(String graphId, Pool jedis) {
- this.graphId = graphId;
- this.client = jedis;
- }
-
- /**
- * Execute a Cypher query with arguments
- *
- * @param query Cypher query
- * @param args
- * @return a result set
- */
- public ResultSet query(String query, Object ...args) {
- if(args.length > 0) {
- for(int i=0; i{
- /**
- * Return the query statistics
- * @return statistics object
- */
- Statistics getStatistics();
+public interface ResultSet extends Iterator {
- List getHeader();
-}
+ public enum ResultSetScalarTypes {
+ PROPERTY_UNKNOWN,
+ PROPERTY_NULL,
+ PROPERTY_STRING,
+ PROPERTY_INTEGER,
+ PROPERTY_BOOLEAN,
+ PROPERTY_DOUBLE,
+ }
+
+ public int size();
+
+ Statistics getStatistics();
+
+ Header getHeader();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/redislabs/redisgraph/Utils.java b/src/main/java/com/redislabs/redisgraph/Utils.java
new file mode 100644
index 0000000..180e8fb
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/Utils.java
@@ -0,0 +1,28 @@
+package com.redislabs.redisgraph;
+
+/**
+ * Utilities class
+ */
+public class Utils {
+
+ /**
+ *
+ * @param str - a string
+ * @return the input string surounded with quotation marks, if needed
+ */
+ public static String quoteString(String str){
+ if(str.startsWith("\"") && str.endsWith("\"")){
+ return str;
+ }
+
+ StringBuilder sb = new StringBuilder(str.length()+2);
+ if(str.charAt(0)!='"'){
+ sb.append('"');
+ }
+ sb.append(str);
+ if (str.charAt(str.length()-1)!= '"'){
+ sb.append('"');
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/redislabs/redisgraph/impl/Edge.java b/src/main/java/com/redislabs/redisgraph/impl/Edge.java
new file mode 100644
index 0000000..4b113fc
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/impl/Edge.java
@@ -0,0 +1,92 @@
+package com.redislabs.redisgraph.impl;
+
+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 {
+
+ //memebers
+ private String relationshipType;
+ private int source;
+ private int 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 int getSource() {
+ return source;
+ }
+
+ /**
+ * @param source - The id of the source node to be set
+ */
+ public void setSource(int source) {
+ this.source = source;
+ }
+
+ /**
+ *
+ * @return the id of the destination node
+ */
+ public int getDestination() {
+ return destination;
+ }
+
+ /**
+ *
+ * @param destination - The id of the destination node to be set
+ */
+ public void setDestination(int 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/com/redislabs/redisgraph/impl/GraphCache.java b/src/main/java/com/redislabs/redisgraph/impl/GraphCache.java
new file mode 100644
index 0000000..15239b3
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/impl/GraphCache.java
@@ -0,0 +1,50 @@
+package com.redislabs.redisgraph.impl;
+
+import com.redislabs.redisgraph.RedisGraph;
+
+/**
+ * A class to store a local cache in the client, for a specific graph.
+ * Holds the labels, property names and relationship types
+ */
+public class GraphCache {
+
+ private final GraphCacheList labels;
+ private final GraphCacheList propertyNames;
+ private final GraphCacheList relationshipTypes;
+
+ /**
+ *
+ * @param graphId - graph Id
+ * @param redisGraph - a client to use in the cache, for re-validate it by calling procedures
+ */
+ public GraphCache(String graphId, RedisGraph redisGraph) {
+ this.labels = new GraphCacheList(graphId, "db.labels", redisGraph);
+ this.propertyNames = new GraphCacheList(graphId, "db.propertyKeys", redisGraph);
+ this.relationshipTypes = new GraphCacheList(graphId, "db.relationshipTypes", redisGraph);
+ }
+
+ /**
+ * @param index - index of label
+ * @return requested label
+ */
+ public String getLabel(int index) {
+ return labels.getCachedData(index);
+ }
+
+ /**
+ * @param index index of the relationship type
+ * @return requested relationship type
+ */
+ public String getRelationshipType(int index) {
+ return relationshipTypes.getCachedData(index);
+ }
+
+ /**
+ * @param index index of property name
+ * @return requested property
+ */
+ public String getPropertyName(int index) {
+
+ return propertyNames.getCachedData(index);
+ }
+}
diff --git a/src/main/java/com/redislabs/redisgraph/impl/GraphCacheList.java b/src/main/java/com/redislabs/redisgraph/impl/GraphCacheList.java
new file mode 100644
index 0000000..58b8b43
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/impl/GraphCacheList.java
@@ -0,0 +1,72 @@
+package com.redislabs.redisgraph.impl;
+
+import com.redislabs.redisgraph.Record;
+import com.redislabs.redisgraph.RedisGraph;
+import com.redislabs.redisgraph.ResultSet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Represents a local cache of list of strings. Holds data from a specific procedure, for a specific graph.
+ */
+public class GraphCacheList {
+
+ private Object mutex = new Object();
+ private final String graphId;
+ private final String procedure;
+ private final RedisGraph redisGraph;
+ private final List data = new CopyOnWriteArrayList<>();
+
+
+
+ /**
+ *
+ * @param graphId - graph id
+ * @param procedure - exact procedure command
+ * @param redisGraph - a client to use in the cache, for re-validate it by calling procedures
+ */
+ public GraphCacheList(String graphId, String procedure, RedisGraph redisGraph) {
+ this.graphId = graphId;
+ this.procedure = procedure;
+ this.redisGraph = redisGraph;
+ }
+
+
+ /**
+ * 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 (mutex){
+ if (index >= data.size()) {
+ getProcedureInfo();
+ }
+ }
+ }
+ String s = data.get(index);
+ return s;
+
+ }
+
+ /**
+ * Auxiliary method to parse a procedure result set and refresh the cache
+ */
+ private void getProcedureInfo() {
+ ResultSet resultSet = redisGraph.callProcedure(graphId, procedure);
+ List newData = new ArrayList<>();
+ int i = 0;
+ while (resultSet.hasNext()) {
+ Record record = resultSet.next();
+ if(i >= data.size()){
+ newData.add(record.getString(0));
+ }
+ i++;
+ }
+ data.addAll(newData);
+ }
+}
diff --git a/src/main/java/com/redislabs/redisgraph/impl/GraphEntity.java b/src/main/java/com/redislabs/redisgraph/impl/GraphEntity.java
new file mode 100644
index 0000000..9357b97
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/impl/GraphEntity.java
@@ -0,0 +1,120 @@
+package com.redislabs.redisgraph.impl;
+
+
+import com.redislabs.redisgraph.ResultSet.ResultSetScalarTypes;
+
+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 {
+
+
+
+ //members
+
+ protected int id;
+ protected final Map propertyMap = new HashMap<>();
+
+
+ //setters & getters
+
+ /**
+ *
+ * @return entity id
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ *
+ * @param id - entity id to be set
+ */
+ public void setId(int id) {
+ this.id = id;
+ }
+
+
+ /**
+ * Adds a property to the entity, by composing name, type and value to a property object
+ * @param name
+ * @param type
+ * @param value
+ */
+ public void addProperty(String name, ResultSetScalarTypes type, Object value){
+
+ addProperty(new Property(name, type, value));
+
+ }
+
+ /**
+ * 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);
+ }
+
+
+ /**
+ * Default toString implementation.
+ * @return
+ */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("GraphEntity{");
+ sb.append("id=").append(id);
+ sb.append(", propertyMap=").append(propertyMap);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java b/src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java
new file mode 100644
index 0000000..2a7a40f
--- /dev/null
+++ b/src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java
@@ -0,0 +1,95 @@
+package com.redislabs.redisgraph.impl;
+
+import com.redislabs.redisgraph.Header;
+import redis.clients.jedis.util.SafeEncoder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Query result header interface implementation
+ */
+public class HeaderImpl implements Header {
+
+ //members
+ private final List> raw;
+ private final List schemaTypes = new ArrayList<>();
+ private final List schemaNames = new ArrayList<>();
+
+
+ /**
+ * Parameterized constructor
+ * A raw representation of a header (query response schema) is a list.
+ * Each entry in the list is a tuple (list of size 2).
+ * tuple[0] represents the type of the column, and tuple[1] represents the name of the column.
+ *
+ * @param raw - raw representation of a header
+ */
+ public HeaderImpl(List> raw) {
+ this.raw = raw;
+ }
+
+
+ /**
+ * @return a list of column names, ordered by they appearance in the query
+ */
+ @Override
+ public List getSchemaNames() {
+ if (schemaNames.size() == 0) {
+ buildSchema();
+ }
+ return schemaNames;
+ }
+
+ /**
+ * @return a list of column types, ordered by they appearance in the query
+ */
+ @Override
+ public List getSchemaTypes() {
+ if (schemaTypes.size() == 0) {
+ buildSchema();
+ }
+ return schemaTypes;
+ }
+
+ /**
+ * Extracts schema names and types from the raw representation
+ */
+ private void buildSchema() {
+ for (List