diff --git a/pom.xml b/pom.xml index 8fd22c7..2b2c133 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ repo - + org.apache.commons @@ -62,7 +62,7 @@ redis.clients jedis - 3.0.1 + 3.1.0 org.apache.commons @@ -179,7 +179,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.11.0 + 3.12.0 diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF index 425ebf5..7356ba4 100644 --- a/src/main/java/META-INF/MANIFEST.MF +++ b/src/main/java/META-INF/MANIFEST.MF @@ -1,3 +1,3 @@ -Manifest-Version: 1.0 -Main-Class: com.redislabs.redisgraph.RedisGraphAPI - +Manifest-Version: 1.0 +Main-Class: com.redislabs.redisgraph.RedisGraph + diff --git a/src/main/java/com/redislabs/redisgraph/Header.java b/src/main/java/com/redislabs/redisgraph/Header.java index 43cf8b3..a8156aa 100644 --- a/src/main/java/com/redislabs/redisgraph/Header.java +++ b/src/main/java/com/redislabs/redisgraph/Header.java @@ -3,16 +3,16 @@ import java.util.List; /** - * Query response header interface. Represents the response schame (column names and types) + * Query response header interface. Represents the response schema (column names and types) */ public interface Header { - public enum ResultSetColumnTypes { + enum ResultSetColumnTypes { COLUMN_UNKNOWN, COLUMN_SCALAR, COLUMN_NODE, - COLUMN_RELATION; + COLUMN_RELATION } diff --git a/src/main/java/com/redislabs/redisgraph/RedisGraph.java b/src/main/java/com/redislabs/redisgraph/RedisGraph.java index 9c0871d..90432bd 100644 --- a/src/main/java/com/redislabs/redisgraph/RedisGraph.java +++ b/src/main/java/com/redislabs/redisgraph/RedisGraph.java @@ -1,101 +1,19 @@ package com.redislabs.redisgraph; -import com.redislabs.redisgraph.impl.graph_cache.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(); - } +import java.util.List; +import java.util.Map; +public interface RedisGraph extends Closeable { /** * 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)); - - } + ResultSet query(String graphId, String query, Object ...args); /** * Invokes stored procedures without arguments @@ -103,10 +21,7 @@ public ResultSet query(String graphId, String query, Object ...args) { * @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<>()); - } - + ResultSet callProcedure(String graphId, String procedure); /** * Invokes stored procedure with arguments @@ -115,58 +30,7 @@ public ResultSet callProcedure(String graphId, String procedure ){ * @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(); - } - + ResultSet callProcedure(String graphId, String procedure, List args); /** * Invoke a stored procedure @@ -174,17 +38,15 @@ private Jedis getConnection() { * @param procedure - procedure to execute * @param args - procedure arguments * @param kwargs - procedure output arguments - * @return + * @return result set with the procedure data + */ + ResultSet callProcedure(String graphId, String procedure, List args , Map> kwargs); + + /** + * Deletes the entire graph + * @param graphId graph to delete + * @return delete running time statistics */ - public ResultSet callProcedure(String graphId, String procedure, List args , Map> kwargs ){ + String deleteGraph(String graphId); - 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/RedisGraphContexted.java b/src/main/java/com/redislabs/redisgraph/RedisGraphContexted.java new file mode 100644 index 0000000..5ef1037 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/RedisGraphContexted.java @@ -0,0 +1,32 @@ +package com.redislabs.redisgraph; + +import redis.clients.jedis.Jedis; + +public interface RedisGraphContexted extends RedisGraph { + + + /** + * Returns implementing class connection context + * @return Jedis connection + */ + Jedis getConnectionContext(); + + /** + * Returns a Redis transactional object, over the connection context, with graph API capabilities + * @return Redis transactional object, over the connection context, with graph API capabilities + */ + RedisGraphTransaction multi(); + + /** + * Perform watch over given Redis keys + * @param keys + * @return "OK" + */ + String watch(String... keys); + + /** + * Removes watch from all keys + * @return + */ + String unwatch(); +} diff --git a/src/main/java/com/redislabs/redisgraph/RedisGraphGeneralContext.java b/src/main/java/com/redislabs/redisgraph/RedisGraphGeneralContext.java new file mode 100644 index 0000000..cdf218a --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/RedisGraphGeneralContext.java @@ -0,0 +1,11 @@ +package com.redislabs.redisgraph; + +public interface RedisGraphGeneralContext extends RedisGraph { + + /** + * Generate a connection bounded api + * @return a connection bounded api + */ + RedisGraphContexted getContextedAPI(); + +} diff --git a/src/main/java/com/redislabs/redisgraph/RedisGraphTransaction.java b/src/main/java/com/redislabs/redisgraph/RedisGraphTransaction.java new file mode 100644 index 0000000..c206640 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/RedisGraphTransaction.java @@ -0,0 +1,91 @@ +package com.redislabs.redisgraph; + +import redis.clients.jedis.Response; +import redis.clients.jedis.commands.BasicRedisPipeline; +import redis.clients.jedis.commands.BinaryRedisPipeline; +import redis.clients.jedis.commands.BinaryScriptingCommandsPipeline; +import redis.clients.jedis.commands.ClusterPipeline; +import redis.clients.jedis.commands.MultiKeyBinaryRedisPipeline; +import redis.clients.jedis.commands.MultiKeyCommandsPipeline; +import redis.clients.jedis.commands.RedisPipeline; +import redis.clients.jedis.commands.ScriptingCommandsPipeline; + +import java.io.Closeable; +import java.util.List; +import java.util.Map; + +/** + * An interface which aligned to Jedis transactional interface + */ +public interface RedisGraphTransaction extends + MultiKeyBinaryRedisPipeline, + MultiKeyCommandsPipeline, ClusterPipeline, + BinaryScriptingCommandsPipeline, ScriptingCommandsPipeline, + BasicRedisPipeline, BinaryRedisPipeline, RedisPipeline, Closeable { + /** + * Execute a Cypher query with arguments + * @param graphId a graph to perform the query on + * @param query Cypher query + * @param args + * @return a response which builds the result set with the query answer + */ + Response query(String graphId, String query, Object ...args); + + /** + * Invokes stored procedures without arguments + * @param graphId a graph to perform the query on + * @param procedure procedure name to invoke + * @return a response which builds result set with the procedure data + */ + Response callProcedure(String graphId, String procedure); + + /** + * 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 a response which builds result set with the procedure data + */ + Response callProcedure(String graphId, String procedure, List args); + + /** + * 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 a response which builds result set with the procedure data + */ + Response callProcedure(String graphId, String procedure, List args , Map> kwargs); + + /** + * Deletes the entire graph + * @param graphId graph to delete + * @return a response which builds the delete running time statistics + */ + Response deleteGraph(String graphId); + + + /** + * executes the transaction + * @return a list of the executed transaction commands answers, in case of successful transaction, null otherwise + */ + List exec(); + + /** + * If object is in transaction mode, + * flushes all previously queued commands in a transaction and restores the connection state to normal + */ + void clear(); + + /** + * + * @return + */ + List> execGetResponse(); + + /** + * Flushes all previously queued commands in a transaction and restores the connection state to normal + */ + String discard(); +} diff --git a/src/main/java/com/redislabs/redisgraph/ResultSet.java b/src/main/java/com/redislabs/redisgraph/ResultSet.java index 7ac933a..251a4d0 100644 --- a/src/main/java/com/redislabs/redisgraph/ResultSet.java +++ b/src/main/java/com/redislabs/redisgraph/ResultSet.java @@ -1,14 +1,13 @@ package com.redislabs.redisgraph; import java.util.Iterator; -import java.util.List; /** * Hold a query result */ public interface ResultSet extends Iterator { - public enum ResultSetScalarTypes { + enum ResultSetScalarTypes { PROPERTY_UNKNOWN, PROPERTY_NULL, PROPERTY_STRING, @@ -17,7 +16,7 @@ public enum ResultSetScalarTypes { PROPERTY_DOUBLE, } - public int size(); + int size(); Statistics getStatistics(); diff --git a/src/main/java/com/redislabs/redisgraph/Utils.java b/src/main/java/com/redislabs/redisgraph/Utils.java deleted file mode 100644 index 180e8fb..0000000 --- a/src/main/java/com/redislabs/redisgraph/Utils.java +++ /dev/null @@ -1,28 +0,0 @@ -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/graph_entities/Edge.java b/src/main/java/com/redislabs/redisgraph/graph_entities/Edge.java index fa30d03..f7c0135 100644 --- a/src/main/java/com/redislabs/redisgraph/graph_entities/Edge.java +++ b/src/main/java/com/redislabs/redisgraph/graph_entities/Edge.java @@ -8,7 +8,7 @@ */ public class Edge extends GraphEntity { - //memebers + //members private String relationshipType; private int source; private int destination; diff --git a/src/main/java/com/redislabs/redisgraph/graph_entities/Node.java b/src/main/java/com/redislabs/redisgraph/graph_entities/Node.java index 6ede861..91b9a4f 100644 --- a/src/main/java/com/redislabs/redisgraph/graph_entities/Node.java +++ b/src/main/java/com/redislabs/redisgraph/graph_entities/Node.java @@ -10,7 +10,7 @@ public class Node extends GraphEntity { //members - final private List labels = new ArrayList<>(); + private final List labels = new ArrayList<>(); /** * @param label - a label to be add @@ -28,11 +28,11 @@ public void removeLabel(String label) { /** * @param index - label index - * @return the proprty label + * @return the property label * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= getNumberOfLabels()}) */ - public String getLabel(int index) throws IndexOutOfBoundsException{ + public String getLabel(int index){ return labels.get(index); } diff --git a/src/main/java/com/redislabs/redisgraph/impl/Utils.java b/src/main/java/com/redislabs/redisgraph/impl/Utils.java new file mode 100644 index 0000000..bb39c5b --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/Utils.java @@ -0,0 +1,101 @@ +package com.redislabs.redisgraph.impl; + +import org.apache.commons.text.translate.AggregateTranslator; +import org.apache.commons.text.translate.CharSequenceTranslator; +import org.apache.commons.text.translate.LookupTranslator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Utilities class + */ +public class Utils { + public static final List DUMMY_LIST = new ArrayList<>(0); + public static final Map> DUMMY_MAP = new HashMap<>(0); + public static final String COMPACT_STRING = "--COMPACT"; + + 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))); + } + + private Utils() {} + + /** + * + * @param str - a string + * @return the input string surrounded with quotation marks, if needed + */ + private 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(); + } + + /** + * Prepare and formats a query and query arguments + * @param query - query + * @param args - query arguments + * @return formatted query + */ + public static String prepareQuery(String query, Object ...args){ + if(args.length > 0) { + for(int i=0; i args , Map> kwargs){ + args = args.stream().map( s -> Utils.quoteString(s)).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/com/redislabs/redisgraph/impl/api/AbstractRedisGraph.java b/src/main/java/com/redislabs/redisgraph/impl/api/AbstractRedisGraph.java new file mode 100644 index 0000000..d659b22 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/api/AbstractRedisGraph.java @@ -0,0 +1,58 @@ +package com.redislabs.redisgraph.impl.api; + +import com.redislabs.redisgraph.RedisGraph; +import com.redislabs.redisgraph.ResultSet; +import com.redislabs.redisgraph.impl.Utils; +import redis.clients.jedis.Jedis; + +import java.util.List; +import java.util.Map; + +/** + * An abstract class to handle non implementation specific user requests + */ +public abstract class AbstractRedisGraph implements RedisGraph { + + + + /** + * Inherited classes should return a Jedis connection, with respect to their context + * @return Jedis connection + */ + protected abstract Jedis getConnection(); + + /** + * Sends a query to the redis graph. Implementation and context dependent + * @param graphId graph to be queried + * @param preparedQuery prepared query + * @return Result set + */ + protected abstract ResultSet sendQuery(String graphId, String preparedQuery); + + /** + * 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) { + String preparedQuery = Utils.prepareQuery(query, args); + return sendQuery(graphId, preparedQuery); + } + + + public ResultSet callProcedure(String graphId, String procedure){ + return callProcedure(graphId, procedure, Utils.DUMMY_LIST, Utils.DUMMY_MAP); + } + + public ResultSet callProcedure(String graphId, String procedure, List args){ + return callProcedure(graphId, procedure, args, Utils.DUMMY_MAP); + } + + public ResultSet callProcedure(String graphId, String procedure, List args , Map> kwargs){ + + String preparedProcedure = Utils.prepareProcedure(procedure, args, kwargs); + return query(graphId, preparedProcedure); + } +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/api/ContextedRedisGraph.java b/src/main/java/com/redislabs/redisgraph/impl/api/ContextedRedisGraph.java new file mode 100644 index 0000000..b2fe9f6 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/api/ContextedRedisGraph.java @@ -0,0 +1,136 @@ +package com.redislabs.redisgraph.impl.api; + +import com.redislabs.redisgraph.RedisGraphContexted; +import com.redislabs.redisgraph.ResultSet; +import com.redislabs.redisgraph.impl.Utils; +import com.redislabs.redisgraph.impl.graph_cache.RedisGraphCaches; +import com.redislabs.redisgraph.impl.resultset.ResultSetImpl; +import redis.clients.jedis.Client; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.util.SafeEncoder; + +import java.util.List; + +/** + * An implementaion of RedisGraphContexted. Allows sending RedisGraph and some Redis commands, + * within a specific connection context + */ +public class ContextedRedisGraph extends AbstractRedisGraph implements RedisGraphContexted, RedisGraphCacheHolder { + + private Jedis connectionContext; + private RedisGraphCaches caches; + + /** + * Generates a new instance with a specific Jedis connection + * @param connectionContext + */ + public ContextedRedisGraph(Jedis connectionContext){ + this.connectionContext = connectionContext; + } + + /** + * Overrides the abstract method. Return the instance only connection + * @return + */ + @Override + protected Jedis getConnection() { + return this.connectionContext; + } + + /** + * Sends the query over the instance only connection + * @param graphId graph to be queried + * @param preparedQuery prepared query + * @return Result set with the query answer + */ + @Override + protected ResultSet sendQuery(String graphId, String preparedQuery) { + Jedis conn = getConnection(); + try { + List rawResponse = (List) conn.sendCommand(RedisGraphCommand.QUERY, graphId, preparedQuery, Utils.COMPACT_STRING); + return new ResultSetImpl(rawResponse, this, caches.getGraphCache(graphId)); + } + catch (Exception e) { + conn.close(); + throw e; + } + } + + /** + * @return Returns the instance Jedis connection. + */ + @Override + public Jedis getConnectionContext() { + return this.connectionContext; + } + + /** + * Creates a new RedisGraphTransaction transactional object + * @return new RedisGraphTransaction + */ + @Override + public RedisGraphTransaction multi() { + Jedis jedis = getConnection(); + Client client = jedis.getClient(); + client.multi(); + client.getOne(); + RedisGraphTransaction transaction = new RedisGraphTransaction(client,this); + transaction.setRedisGraphCaches(caches); + return transaction; + } + + /** + * Perfrom watch over given Redis keys + * @param keys + * @return "OK" + */ + @Override + public String watch(String... keys) { + return this.getConnection().watch(keys); + } + + /** + * Removes watch from all keys + * @return + */ + @Override + public String unwatch() { + return this.getConnection().unwatch(); + } + + /** + * Deletes the entire graph + * @param graphId graph to delete + * @return delete running time statistics + */ + @Override + public String deleteGraph(String graphId) { + Jedis conn = getConnection(); + Object response; + try { + response = conn.sendCommand(RedisGraphCommand.DELETE, graphId); + } + catch (Exception e) { + conn.close(); + throw e; + } + //clear local state + caches.removeGraphCache(graphId); + return SafeEncoder.encode((byte[]) response); + } + + /** + * closes the Jedis connection + */ + @Override + public void close() { + this.connectionContext.close(); + + } + + @Override + public void setRedisGraphCaches(RedisGraphCaches caches) { + this.caches = caches; + } + +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraph.java b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraph.java new file mode 100644 index 0000000..900e32c --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraph.java @@ -0,0 +1,107 @@ +package com.redislabs.redisgraph.impl.api; + +import com.redislabs.redisgraph.RedisGraphContexted; +import com.redislabs.redisgraph.RedisGraphGeneralContext; +import com.redislabs.redisgraph.ResultSet; +import com.redislabs.redisgraph.impl.graph_cache.RedisGraphCaches; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.util.Pool; +import redis.clients.jedis.util.SafeEncoder; + +/** + * + */ +public class RedisGraph extends AbstractRedisGraph implements RedisGraphGeneralContext { + + private final Pool client; + private RedisGraphCaches caches = new RedisGraphCaches(); + + /** + * 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; + } + + + /** + * Overrides the abstract function. Gets and returns a Jedis connection from the Jedis pool + * @return a Jedis connection + */ + @Override + protected Jedis getConnection() { + return client.getResource(); + } + + /** + * Overrides the abstract function. + * Sends the query from any Jedis connection received from the Jedis pool and closes it once done + * @param graphId graph to be queried + * @param preparedQuery prepared query + * @return Result set with the query answer + */ + @Override + protected ResultSet sendQuery(String graphId, String preparedQuery){ + try (ContextedRedisGraph contextedRedisGraph = new ContextedRedisGraph(getConnection())) { + contextedRedisGraph.setRedisGraphCaches(caches); + return contextedRedisGraph.sendQuery(graphId, preparedQuery); + } + } + + + /** + * Closes the Jedis pool + */ + @Override + public void close(){ + this.client.close(); + } + + + /** + * Deletes the entire graph + * @param graphId graph to delete + * @return delete running time statistics + */ + @Override + public String deleteGraph(String graphId) { + try (Jedis conn = getConnection()) { + Object response = conn.sendCommand(RedisGraphCommand.DELETE, graphId); + //clear local state + caches.removeGraphCache(graphId); + return SafeEncoder.encode((byte[]) response); + } + } + + /** + * Returns a new ContextedRedisGraph bounded to a Jedis connection from the Jedis pool + * @return ContextedRedisGraph + */ + @Override + public RedisGraphContexted getContextedAPI() { + ContextedRedisGraph contextedRedisGraph = new ContextedRedisGraph(getConnection()); + contextedRedisGraph.setRedisGraphCaches(this.caches); + return contextedRedisGraph; + } +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCacheHolder.java b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCacheHolder.java new file mode 100644 index 0000000..97cab72 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCacheHolder.java @@ -0,0 +1,8 @@ +package com.redislabs.redisgraph.impl.api; + +import com.redislabs.redisgraph.impl.graph_cache.RedisGraphCaches; + +public interface RedisGraphCacheHolder { + + void setRedisGraphCaches(RedisGraphCaches caches); +} diff --git a/src/main/java/com/redislabs/redisgraph/Command.java b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCommand.java similarity index 68% rename from src/main/java/com/redislabs/redisgraph/Command.java rename to src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCommand.java index 35dbd98..3db45d0 100644 --- a/src/main/java/com/redislabs/redisgraph/Command.java +++ b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphCommand.java @@ -1,4 +1,4 @@ -package com.redislabs.redisgraph; +package com.redislabs.redisgraph.impl.api; import redis.clients.jedis.util.SafeEncoder; import redis.clients.jedis.commands.ProtocolCommand; @@ -7,13 +7,13 @@ * * */ -public enum Command implements ProtocolCommand { +public enum RedisGraphCommand implements ProtocolCommand { QUERY("graph.QUERY"), DELETE("graph.DELETE"); private final byte[] raw; - Command(String alt) { + RedisGraphCommand(String alt) { raw = SafeEncoder.encode(alt); } diff --git a/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphTransaction.java b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphTransaction.java new file mode 100644 index 0000000..88660cc --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/api/RedisGraphTransaction.java @@ -0,0 +1,108 @@ +package com.redislabs.redisgraph.impl.api; + +import com.redislabs.redisgraph.RedisGraph; +import com.redislabs.redisgraph.ResultSet; +import com.redislabs.redisgraph.impl.Utils; +import com.redislabs.redisgraph.impl.graph_cache.RedisGraphCaches; +import com.redislabs.redisgraph.impl.resultset.ResultSetImpl; +import redis.clients.jedis.Builder; +import redis.clients.jedis.BuilderFactory; +import redis.clients.jedis.Client; +import redis.clients.jedis.Response; +import redis.clients.jedis.Transaction; + +import java.util.List; +import java.util.Map; + +/** + * This class is extending Jedis Transaction + */ +public class RedisGraphTransaction extends Transaction implements com.redislabs.redisgraph.RedisGraphTransaction, RedisGraphCacheHolder { + + private final RedisGraph redisGraph; + private RedisGraphCaches caches; + + + public RedisGraphTransaction(Client client, RedisGraph redisGraph){ + // init as in Jedis + super(client); + + this.redisGraph = redisGraph; + } + + /** + * Execute a Cypher query with arguments + * + * @param graphId a graph to perform the query on + * @param query Cypher query + * @param args + * @return response with a result set + */ + public Response query(String graphId, String query, Object ...args){ + String preparedQuery = Utils.prepareQuery(query, args); + client.sendCommand(RedisGraphCommand.QUERY, graphId, preparedQuery, "--COMPACT"); + return getResponse(new Builder() { + @Override + public ResultSet build(Object o) { + return new ResultSetImpl((List)o, redisGraph, caches.getGraphCache(graphId)); + } + }); + } + + + /** + * Invokes stored procedures without arguments, in multi/exec context + * @param graphId a graph to perform the query on + * @param procedure procedure name to invoke + * @return response with result set with the procedure data + */ + public Response callProcedure(String graphId, String procedure){ + return callProcedure(graphId, procedure, Utils.DUMMY_LIST, Utils.DUMMY_MAP); + } + + /** + * Invokes stored procedure with arguments, in multi/exec context + * @param graphId a graph to perform the query on + * @param procedure procedure name to invoke + * @param args procedure arguments + * @return response with result set with the procedure data + */ + public Response callProcedure(String graphId, String procedure, List args ){ + return callProcedure(graphId, procedure, args, Utils.DUMMY_MAP); + } + + + /** + * Invoke a stored procedure, in multi/exec context + * @param graphId a graph to perform the query on + * @param procedure - procedure to execute + * @param args - procedure arguments + * @param kwargs - procedure output arguments + * @return response with result set with the procedure data + */ + public Response callProcedure(String graphId, String procedure, List args, + Map> kwargs) { + String preparedProcedure = Utils.prepareProcedure(procedure, args, kwargs); + return query(graphId, preparedProcedure); + } + + + /** + * Deletes the entire graph, in multi/exec context + * @param graphId graph to delete + * @return response with the deletion running time statistics + */ + public Response deleteGraph(String graphId){ + + client.sendCommand(RedisGraphCommand.DELETE, graphId); + Response response = getResponse(BuilderFactory.STRING); + caches.removeGraphCache(graphId); + return response; + } + + @Override + public void setRedisGraphCaches(RedisGraphCaches caches) { + this.caches = caches; + } + +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCache.java b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCache.java index 765dff8..565c916 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCache.java +++ b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCache.java @@ -15,36 +15,35 @@ public class GraphCache { /** * * @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); + public GraphCache(String graphId) { + this.labels = new GraphCacheList(graphId, "db.labels"); + this.propertyNames = new GraphCacheList(graphId, "db.propertyKeys"); + this.relationshipTypes = new GraphCacheList(graphId, "db.relationshipTypes"); } /** * @param index - index of label * @return requested label */ - public String getLabel(int index) { - return labels.getCachedData(index); + public String getLabel(int index, RedisGraph redisGraph) { + return labels.getCachedData(index, redisGraph); } /** * @param index index of the relationship type * @return requested relationship type */ - public String getRelationshipType(int index) { - return relationshipTypes.getCachedData(index); + public String getRelationshipType(int index, RedisGraph redisGraph) { + return relationshipTypes.getCachedData(index, redisGraph); } /** * @param index index of property name * @return requested property */ - public String getPropertyName(int index) { + public String getPropertyName(int index, RedisGraph redisGraph) { - return propertyNames.getCachedData(index); + return propertyNames.getCachedData(index, redisGraph); } } diff --git a/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCacheList.java b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCacheList.java index d608f16..55035d1 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCacheList.java +++ b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/GraphCacheList.java @@ -7,31 +7,25 @@ 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 { +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) { + public GraphCacheList(String graphId, String procedure) { this.graphId = graphId; this.procedure = procedure; - this.redisGraph = redisGraph; } @@ -40,23 +34,22 @@ public GraphCacheList(String graphId, String procedure, RedisGraph redisGraph) { * @param index index of data item * @return The string value of the specific procedure response, at the given index. */ - public String getCachedData(int index) { + public String getCachedData(int index, RedisGraph redisGraph) { if (index >= data.size()) { - synchronized (mutex){ + synchronized (data){ if (index >= data.size()) { - getProcedureInfo(); + getProcedureInfo(redisGraph); } } } - String s = data.get(index); - return s; + return data.get(index); } /** * Auxiliary method to parse a procedure result set and refresh the cache */ - private void getProcedureInfo() { + private void getProcedureInfo(RedisGraph redisGraph) { ResultSet resultSet = redisGraph.callProcedure(graphId, procedure); List newData = new ArrayList<>(); int i = 0; diff --git a/src/main/java/com/redislabs/redisgraph/impl/graph_cache/RedisGraphCaches.java b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/RedisGraphCaches.java new file mode 100644 index 0000000..528ed34 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/impl/graph_cache/RedisGraphCaches.java @@ -0,0 +1,59 @@ +package com.redislabs.redisgraph.impl.graph_cache; + +import com.redislabs.redisgraph.RedisGraph; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class RedisGraphCaches { + + private final Map graphCaches = new ConcurrentHashMap<>(); + + public GraphCache getGraphCache(String graphId){ + if (!graphCaches.containsKey(graphId)){ + graphCaches.putIfAbsent(graphId, new GraphCache(graphId)); + } + return graphCaches.get(graphId); + } + + /** + * Returns a String which represents the name of the label mapped to the label id + * @param graphId graph to perform the query + * @param index label index + * @param redisGraph RedisGraphAPI implementation + * @return label name + */ + public String getLabel(String graphId, int index, RedisGraph redisGraph) { + return getGraphCache(graphId).getLabel(index, redisGraph); + } + + /** + * Returns a String which represents the name of the relationship mapped to the label id + * @param graphId graph to perform the query + * @param index relationship index + * @param redisGraph RedisGraphAPI implementation + * @return relationship name + */ + public String getRelationshipType(String graphId, int index, RedisGraph redisGraph){ + return getGraphCache(graphId).getRelationshipType(index, redisGraph); + } + + /** + * Returns a String which represents the name of the property mapped to the label id + * @param graphId graph to perform the query + * @param index property index + * @param redisGraph RedisGraphAPI implementation + * @return property name + */ + public String getPropertyName(String graphId, int index, RedisGraph redisGraph){ + return getGraphCache(graphId).getPropertyName(index, redisGraph); + } + + /** + * Removes a graph meta data cache + * @param graphId + */ + public void removeGraphCache(String graphId){ + graphCaches.remove(graphId); + } +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/HeaderImpl.java similarity index 98% rename from src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java rename to src/main/java/com/redislabs/redisgraph/impl/resultset/HeaderImpl.java index 2a7a40f..a9ae068 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/HeaderImpl.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/HeaderImpl.java @@ -1,4 +1,4 @@ -package com.redislabs.redisgraph.impl; +package com.redislabs.redisgraph.impl.resultset; import com.redislabs.redisgraph.Header; import redis.clients.jedis.util.SafeEncoder; diff --git a/src/main/java/com/redislabs/redisgraph/impl/RecordImpl.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/RecordImpl.java similarity index 97% rename from src/main/java/com/redislabs/redisgraph/impl/RecordImpl.java rename to src/main/java/com/redislabs/redisgraph/impl/resultset/RecordImpl.java index 8aca26c..adf68ad 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/RecordImpl.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/RecordImpl.java @@ -1,4 +1,4 @@ -package com.redislabs.redisgraph.impl; +package com.redislabs.redisgraph.impl.resultset; import java.util.List; import java.util.Objects; diff --git a/src/main/java/com/redislabs/redisgraph/impl/ResultSetImpl.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java similarity index 87% rename from src/main/java/com/redislabs/redisgraph/impl/ResultSetImpl.java rename to src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java index 9cfc5b8..fd208b6 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/ResultSetImpl.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java @@ -1,4 +1,4 @@ -package com.redislabs.redisgraph.impl; +package com.redislabs.redisgraph.impl.resultset; import com.redislabs.redisgraph.*; import com.redislabs.redisgraph.graph_entities.Edge; @@ -20,15 +20,17 @@ public class ResultSetImpl implements ResultSet { private final List results ; private int position = 0; - private final GraphCache graphCache; + private final RedisGraph redisGraph; + private final GraphCache cache; /** * @param rawResponse the raw representation of response is at most 3 lists of objects. * The last list is the statistics list. - * @param graphCache, the graph local cache + * @param redisGraph, the graph local cache */ - public ResultSetImpl(List rawResponse, GraphCache graphCache) { - this.graphCache = graphCache; + public ResultSetImpl(List rawResponse, RedisGraph redisGraph, GraphCache cache) { + this.redisGraph = redisGraph; + this.cache = cache; if (rawResponse.size() != 3) { header = parseHeader(new ArrayList<>()); @@ -40,7 +42,7 @@ public ResultSetImpl(List rawResponse, GraphCache graphCache) { header = parseHeader((List>) rawResponse.get(0)); results = parseResult((List>) rawResponse.get(1)); - statistics = parseStatistics((List) rawResponse.get(2)); + statistics = parseStatistics(rawResponse.get(2)); } } @@ -75,8 +77,13 @@ private List parseResult(List> rawResultSet) { break; case COLUMN_SCALAR: { parsedRow.add(deserializeScalar(obj)); - + break; } + default: { + parsedRow.add(null); + break; + } + } } @@ -129,8 +136,8 @@ 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((int) labelIndex); + for (Long labelIndex : labelsIndices) { + String label = cache.getLabel(labelIndex.intValue(), redisGraph); node.addLabel(label); } deserializeGraphEntityProperties(node, (List>) rawNodeData.get(2)); @@ -144,7 +151,7 @@ private Node deserializeNode(List rawNodeData) { * @param rawEntityId raw representation of entity id to be set to the graph entity */ private void deserializeGraphEntityId(GraphEntity graphEntity, Object rawEntityId) { - int id = (int) (long) rawEntityId; + int id = ((Long) rawEntityId).intValue(); graphEntity.setId(id); } @@ -162,7 +169,8 @@ private Edge deserializeEdge(List rawEdgeData) { Edge edge = new Edge(); deserializeGraphEntityId(edge, rawEdgeData.get(0)); - String relationshipType = graphCache.getRelationshipType(((Long) rawEdgeData.get(1)).intValue()); + String relationshipType = cache.getRelationshipType(((Long) rawEdgeData.get(1)).intValue(), + redisGraph); edge.setRelationshipType(relationshipType); edge.setSource((int) (long) rawEdgeData.get(2)); @@ -181,12 +189,13 @@ private Edge deserializeEdge(List rawEdgeData) { * rawProperty.get(1) - property type * rawProperty.get(2) - property value */ - void deserializeGraphEntityProperties(GraphEntity entity, List> rawProperties) { + private void deserializeGraphEntityProperties(GraphEntity entity, List> rawProperties) { for (List rawProperty : rawProperties) { Property property = new Property(); - property.setName(graphCache.getPropertyName(((Long) rawProperty.get(0)).intValue())); + property.setName(cache.getPropertyName(((Long) rawProperty.get(0)).intValue(), + redisGraph)); //trimmed for getting to value using deserializeScalar List propertyScalar = rawProperty.subList(1, rawProperty.size()); @@ -214,7 +223,7 @@ private Object deserializeScalar(List rawScalarData) { case PROPERTY_DOUBLE: return Double.parseDouble(SafeEncoder.encode((byte[]) obj)); case PROPERTY_INTEGER: - return (Integer) ((Long) obj).intValue(); + return ((Long) obj).intValue(); case PROPERTY_STRING: return SafeEncoder.encode((byte[]) obj); case PROPERTY_UNKNOWN: diff --git a/src/main/java/com/redislabs/redisgraph/impl/StatisticsImpl.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/StatisticsImpl.java similarity index 93% rename from src/main/java/com/redislabs/redisgraph/impl/StatisticsImpl.java rename to src/main/java/com/redislabs/redisgraph/impl/resultset/StatisticsImpl.java index 68887e1..a6b24af 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/StatisticsImpl.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/StatisticsImpl.java @@ -1,4 +1,4 @@ -package com.redislabs.redisgraph.impl; +package com.redislabs.redisgraph.impl.resultset; import java.util.EnumMap; import java.util.List; @@ -18,7 +18,7 @@ public class StatisticsImpl implements Statistics { private final Map statistics; /** - * A raw representation of query exection statistics is a list of strings + * A raw representation of query execution statistics is a list of strings * (byte arrays which need to be de-serialized). * Each string is built in the form of "K:V" where K is statistics label and V is its value. * @param raw a raw representation of the query execution statistics @@ -95,7 +95,7 @@ public int indicesAdded() { /** * - * @return number of lables added after query execution + * @return number of labels added after query execution */ @Override public int labelsAdded() { @@ -134,7 +134,7 @@ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof StatisticsImpl)) return false; StatisticsImpl that = (StatisticsImpl) o; - return Objects.equals(raw, raw) && + return Objects.equals(raw, that.raw) && Objects.equals(getStatistics(), that.getStatistics()); } diff --git a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java index bf4be21..6c5514e 100644 --- a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java +++ b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java @@ -1,6 +1,7 @@ package com.redislabs.redisgraph; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; @@ -10,6 +11,8 @@ import com.redislabs.redisgraph.graph_entities.Edge; import com.redislabs.redisgraph.graph_entities.Node; import com.redislabs.redisgraph.graph_entities.Property; +import com.redislabs.redisgraph.impl.api.RedisGraph; +import com.redislabs.redisgraph.impl.resultset.ResultSetImpl; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -20,7 +23,7 @@ import static com.redislabs.redisgraph.Header.ResultSetColumnTypes.*; public class RedisGraphAPITest { - RedisGraph api; + private RedisGraphGeneralContext api; public RedisGraphAPITest() { } @@ -33,11 +36,14 @@ public void createApi(){ public void deleteGraph() { api.deleteGraph("social"); - api.close(); + try { + api.close(); + } catch (IOException e) { + e.printStackTrace(); + } } - @Test public void testCreateNode() { // Create a node @@ -57,7 +63,7 @@ public void testCreateNode() { try { resultSet.next(); Assert.fail(); - } catch (NoSuchElementException e) { + } catch (NoSuchElementException ignored) { } } @@ -494,4 +500,276 @@ public void testEscapedQuery() { Assert.assertNotNull(api.query("social", "MATCH (n) where n.s1=%s and n.s2=%s RETURN n", "S\"\'", "S\\'\\\"")); Assert.assertNotNull(api.query("social", "MATCH (n) where n.s1='S\"\\'' RETURN n")); } + + + @Test + public void testMultiExec(){ + RedisGraphTransaction transaction = api.getContextedAPI().multi(); + + transaction.set("x", "1"); + transaction.query("social", "CREATE (:Person {name:'a'})"); + transaction.query("g", "CREATE (:Person {name:'a'})"); + transaction.incr("x"); + transaction.get("x"); + transaction.query("social", "MATCH (n:Person) RETURN n"); + transaction.deleteGraph("g"); + transaction.callProcedure("social", "db.labels"); + List results = transaction.exec(); + + // Redis set command + Assert.assertEquals(String.class, results.get(0).getClass()); + Assert.assertEquals("OK", results.get(0)); + + // Redis graph command + Assert.assertEquals(ResultSetImpl.class, results.get(1).getClass()); + ResultSet resultSet = (ResultSet) results.get(1); + Assert.assertEquals(1, resultSet.getStatistics().nodesCreated()); + Assert.assertEquals(1, resultSet.getStatistics().propertiesSet()); + + + Assert.assertEquals(ResultSetImpl.class, results.get(2).getClass()); + resultSet = (ResultSet) results.get(2); + Assert.assertEquals(1, resultSet.getStatistics().nodesCreated()); + Assert.assertEquals(1, resultSet.getStatistics().propertiesSet()); + + // Redis incr command + Assert.assertEquals(Long.class, results.get(3).getClass()); + Assert.assertEquals((long)2, results.get(3)); + + // Redis get command + Assert.assertEquals(String.class, results.get(4).getClass()); + Assert.assertEquals("2", results.get(4)); + + // Graph query result + Assert.assertEquals(ResultSetImpl.class, results.get(5).getClass()); + resultSet = (ResultSet) results.get(5); + + Assert.assertNotNull(resultSet.getHeader()); + Header header = resultSet.getHeader(); + + + List schemaNames = header.getSchemaNames(); + List schemaTypes = header.getSchemaTypes(); + + Assert.assertNotNull(schemaNames); + Assert.assertNotNull(schemaTypes); + + Assert.assertEquals(1, schemaNames.size()); + Assert.assertEquals(1, schemaTypes.size()); + + Assert.assertEquals("n", schemaNames.get(0)); + + Assert.assertEquals(COLUMN_NODE, schemaTypes.get(0)); + + Property nameProperty = new Property("name", ResultSet.ResultSetScalarTypes.PROPERTY_STRING, "a"); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("Person"); + expectedNode.addProperty(nameProperty); + // see that the result were pulled from the right graph + Assert.assertEquals(1, resultSet.size()); + Assert.assertTrue(resultSet.hasNext()); + Record record = resultSet.next(); + Assert.assertFalse(resultSet.hasNext()); + Assert.assertEquals(Arrays.asList("n"), record.keys()); + Assert.assertEquals(expectedNode, record.getValue("n")); + + // Graph delete + Assert.assertTrue(((String)results.get(6)).startsWith("Graph removed")); + + + Assert.assertEquals(ResultSetImpl.class, results.get(7).getClass()); + resultSet = (ResultSet) results.get(7); + + Assert.assertNotNull(resultSet.getHeader()); + header = resultSet.getHeader(); + + + schemaNames = header.getSchemaNames(); + schemaTypes = header.getSchemaTypes(); + + Assert.assertNotNull(schemaNames); + Assert.assertNotNull(schemaTypes); + + Assert.assertEquals(1, schemaNames.size()); + Assert.assertEquals(1, schemaTypes.size()); + + Assert.assertEquals("label", schemaNames.get(0)); + + Assert.assertEquals(COLUMN_SCALAR, schemaTypes.get(0)); + + Assert.assertEquals(1, resultSet.size()); + Assert.assertTrue(resultSet.hasNext()); + record = resultSet.next(); + Assert.assertFalse(resultSet.hasNext()); + Assert.assertEquals(Arrays.asList("label"), record.keys()); + Assert.assertEquals("Person", record.getValue("label")); + + } + + @Test + public void testContextedAPI(){ + + String name = "roi"; + int age = 32; + double doubleValue = 3.14; + boolean boolValue = true; + + String place = "TLV"; + int since = 2000; + + + + Property nameProperty = new Property("name", ResultSet.ResultSetScalarTypes.PROPERTY_STRING, name); + Property ageProperty = new Property("age", ResultSet.ResultSetScalarTypes.PROPERTY_INTEGER, age); + Property doubleProperty = new Property("doubleValue", ResultSet.ResultSetScalarTypes.PROPERTY_DOUBLE, doubleValue); + Property trueBooleanProperty = new Property("boolValue", ResultSet.ResultSetScalarTypes.PROPERTY_BOOLEAN, true); + Property falseBooleanProperty = new Property("boolValue", ResultSet.ResultSetScalarTypes.PROPERTY_BOOLEAN, false); + Property nullProperty = new Property("nullValue", ResultSet.ResultSetScalarTypes.PROPERTY_NULL, null); + + Property placeProperty = new Property("place", ResultSet.ResultSetScalarTypes.PROPERTY_STRING, place); + Property sinceProperty = new Property("since", ResultSet.ResultSetScalarTypes.PROPERTY_INTEGER, since); + + Node expectedNode = new Node(); + expectedNode.setId(0); + expectedNode.addLabel("person"); + expectedNode.addProperty(nameProperty); + expectedNode.addProperty(ageProperty); + expectedNode.addProperty(doubleProperty); + expectedNode.addProperty(trueBooleanProperty); + expectedNode.addProperty(nullProperty); + + 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); + expectedEdge.addProperty(nullProperty); + + RedisGraphContexted c = api.getContextedAPI(); + + Assert.assertNotNull(c.query("social", "CREATE (:person{name:%s',age:%d, doubleValue:%f, boolValue:%b, nullValue:null})", name, age, doubleValue, boolValue)); + Assert.assertNotNull(c.query("social", "CREATE (:person{name:'amit',age:30})")); + Assert.assertNotNull(c.query("social", "MATCH (a:person), (b:person) WHERE (a.name = 'roi' AND b.name='amit') " + + "CREATE (a)-[:knows{place:'TLV', since:2000,doubleValue:3.14, boolValue:false, nullValue:null}]->(b)")); + + ResultSet resultSet = c.query("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a,r, " + + "a.name, a.age, a.doubleValue, a.boolValue, a.nullValue, " + + "r.place, r.since, r.doubleValue, r.boolValue, r.nullValue"); + Assert.assertNotNull(resultSet); + + + Assert.assertEquals(0, resultSet.getStatistics().nodesCreated()); + Assert.assertEquals(0, resultSet.getStatistics().nodesDeleted()); + Assert.assertEquals(0, resultSet.getStatistics().labelsAdded()); + Assert.assertEquals(0, resultSet.getStatistics().propertiesSet()); + Assert.assertEquals(0, resultSet.getStatistics().relationshipsCreated()); + Assert.assertEquals(0, resultSet.getStatistics().relationshipsDeleted()); + Assert.assertNotNull(resultSet.getStatistics().getStringValue(Label.QUERY_INTERNAL_EXECUTION_TIME)); + + + Assert.assertEquals(1, resultSet.size()); + Assert.assertTrue(resultSet.hasNext()); + Record record = resultSet.next(); + Assert.assertFalse(resultSet.hasNext()); + + Node node = record.getValue(0); + Assert.assertNotNull(node); + + Assert.assertEquals(expectedNode, node); + + node = record.getValue("a"); + Assert.assertEquals(expectedNode, node); + + Edge edge = record.getValue(1); + Assert.assertNotNull(edge); + Assert.assertEquals(expectedEdge, edge); + + edge = record.getValue("r"); + Assert.assertEquals(expectedEdge, edge); + + Assert.assertEquals(Arrays.asList("a", "r", "a.name", "a.age", "a.doubleValue", "a.boolValue", "a.nullValue", + "r.place", "r.since", "r.doubleValue", "r.boolValue", "r.nullValue"), record.keys()); + + Assert.assertEquals(Arrays.asList(expectedNode, expectedEdge, + name, age, doubleValue, true, null, + place, since, doubleValue, false, null), + record.values()); + + Node a = record.getValue("a"); + for (String propertyName : expectedNode.getEntityPropertyNames()){ + Assert.assertEquals(expectedNode.getProperty(propertyName) ,a.getProperty(propertyName)); + } + + Assert.assertEquals( "roi", record.getString(2)); + Assert.assertEquals( "32", record.getString(3)); + Assert.assertEquals( 32L, ((Integer)(record.getValue(3))).longValue()); + Assert.assertEquals( 32L, ((Integer)record.getValue("a.age")).longValue()); + Assert.assertEquals( "roi", record.getString("a.name")); + Assert.assertEquals( "32", record.getString("a.age")); + + + } + + @Test + public void testWriteTransactionWatch(){ + + RedisGraphContexted c1 = api.getContextedAPI(); + RedisGraphContexted c2 = api.getContextedAPI(); + + c1.watch("social"); + RedisGraphTransaction t1 = c1.multi(); + + + t1.query("social", "CREATE (:Person {name:'a'})"); + c2.query("social", "CREATE (:Person {name:'b'})"); + List returnValue = t1.exec(); + Assert.assertNull(returnValue); + + try { + c1.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + c2.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + @Test + public void testReadTransactionWatch(){ + + RedisGraphContexted c1 = api.getContextedAPI(); + RedisGraphContexted c2 = api.getContextedAPI(); + Assert.assertNotEquals(c1.getConnectionContext(), c2.getConnectionContext()); + c1.query("social", "CREATE (:Person {name:'a'})"); + c1.watch("social"); + RedisGraphTransaction t1 = c1.multi(); + + t1.query("social", "CREATE (:Person {name:'b'})"); + c2.query("social", "MATCH (n) return n"); + List returnValue = t1.exec(); + + Assert.assertNotNull(returnValue); + + try { + c1.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + c2.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } }