package com.redislabs.redisgraph.impl.resultset; import com.redislabs.redisgraph.Header; import com.redislabs.redisgraph.Record; import com.redislabs.redisgraph.RedisGraph; import com.redislabs.redisgraph.ResultSet; import com.redislabs.redisgraph.Statistics; import com.redislabs.redisgraph.exceptions.JRedisGraphRunTimeException; import com.redislabs.redisgraph.graph_entities.*; import com.redislabs.redisgraph.impl.graph_cache.GraphCache; import redis.clients.jedis.util.SafeEncoder; import redis.clients.jedis.exceptions.JedisDataException; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; public class ResultSetImpl implements ResultSet { private final Header header; private final Statistics statistics; private final List<Record> results ; private int position = 0; 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 redisGraph, the graph local cache */ public ResultSetImpl(List<Object> rawResponse, RedisGraph redisGraph, GraphCache cache) { this.redisGraph = redisGraph; this.cache = cache; // If a run-time error occured, the last member of the rawResponse will be a JedisDataException. if (rawResponse.get(rawResponse.size()-1) instanceof JedisDataException) { throw new JRedisGraphRunTimeException((Throwable) rawResponse.get(rawResponse.size() - 1)); } if (rawResponse.size() != 3) { header = parseHeader(new ArrayList<>()); results = new ArrayList<>(); statistics = rawResponse.size()> 0 ? parseStatistics(rawResponse.get(rawResponse.size() - 1)) : parseStatistics(new ArrayList<Objects>()); } else { header = parseHeader((List<List<Object>>) rawResponse.get(0)); results = parseResult((List<List<Object>>) rawResponse.get(1)); statistics = parseStatistics(rawResponse.get(2)); } } /** * * @param rawResultSet - raw result set representation * @return parsed result set */ private List<Record> parseResult(List<List<Object>> rawResultSet) { List<Record> results = new ArrayList<>(); if (rawResultSet == null || rawResultSet.isEmpty()) { return results; } else { //go over each raw result for (List<Object> row : rawResultSet) { List<Object> 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<Object> obj = (List<Object>) row.get(i); //get object type Header.ResultSetColumnTypes objType = header.getSchemaTypes().get(i); //deserialize according to type and switch (objType) { case COLUMN_NODE: parsedRow.add(deserializeNode(obj)); break; case COLUMN_RELATION: parsedRow.add(deserializeEdge(obj)); break; case COLUMN_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 rawStatistics raw statistics representation * @return parsed statistics */ private StatisticsImpl parseStatistics(Object rawStatistics) { return new StatisticsImpl((List<byte[]>) rawStatistics); } /** * * @param rawHeader - raw header representation * @return parsed header */ private HeaderImpl parseHeader(List<List<Object>> rawHeader) { return new HeaderImpl(rawHeader); } @Override public Statistics getStatistics() { return statistics; } @Override public Header getHeader() { return header; } /** * @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 */ private Node deserializeNode(List<Object> rawNodeData) { Node node = new Node(); deserializeGraphEntityId(node, rawNodeData.get(0)); List<Long> labelsIndices = (List<Long>) rawNodeData.get(1); for (Long labelIndex : labelsIndices) { String label = cache.getLabel(labelIndex.intValue(), redisGraph); node.addLabel(label); } deserializeGraphEntityProperties(node, (List<List<Object>>) 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 */ private Edge deserializeEdge(List<Object> rawEdgeData) { Edge edge = new Edge(); deserializeGraphEntityId(edge, rawEdgeData.get(0)); String relationshipType = cache.getRelationshipType(((Long) rawEdgeData.get(1)).intValue(), redisGraph); edge.setRelationshipType(relationshipType); edge.setSource( (long) rawEdgeData.get(2)); edge.setDestination( (long) rawEdgeData.get(3)); deserializeGraphEntityProperties(edge, (List<List<Object>>) 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<List<Object>> rawProperties) { for (List<Object> rawProperty : rawProperties) { Property property = new Property(); property.setName(cache.getPropertyName(((Long) rawProperty.get(0)).intValue(), redisGraph)); //trimmed for getting to value using deserializeScalar List<Object> propertyScalar = rawProperty.subList(1, rawProperty.size()); property.setValue(deserializeScalar(propertyScalar)); entity.addProperty(property); } } /** * @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 */ private Object deserializeScalar(List<Object> rawScalarData) { ResultSetScalarTypes type = getValueTypeFromObject(rawScalarData.get(0)); Object obj = rawScalarData.get(1); switch (type) { case VALUE_NULL: return null; case VALUE_BOOLEAN: return Boolean.parseBoolean(SafeEncoder.encode((byte[]) obj)); case VALUE_DOUBLE: return Double.parseDouble(SafeEncoder.encode((byte[]) obj)); case VALUE_INTEGER: return (Long) obj; case VALUE_STRING: return SafeEncoder.encode((byte[]) obj); case VALUE_ARRAY: return deserializeArray(obj); case VALUE_NODE: return deserializeNode((List<Object>) obj); case VALUE_EDGE: return deserializeEdge((List<Object>) obj); case VALUE_PATH: return deserializePath(obj); case VALUE_UNKNOWN: default: return obj; } } private Path deserializePath(Object rawScalarData) { List<List<Object>> array = (List<List<Object>>) rawScalarData; List<Node> nodes = (List<Node>) deserializeScalar(array.get(0)); List<Edge> edges = (List<Edge>) deserializeScalar(array.get(1)); return new Path(nodes, edges); } private List<Object> deserializeArray(Object rawScalarData) { List<List<Object>> array = (List<List<Object>>) rawScalarData; List<Object> res = new ArrayList<>(array.size()); for (List<Object> arrayValue : array) { res.add(deserializeScalar(arrayValue)); } return res; } /** * Auxiliary function to retrieve scalar types * * @param rawScalarType * @return scalar type */ private ResultSetScalarTypes getValueTypeFromObject(Object rawScalarType) { return ResultSetScalarTypes.getValue(((Long) rawScalarType).intValue()); } @Override public boolean hasNext() { return position < results.size(); } @Override public Record next() { if (!hasNext()) throw new NoSuchElementException(); return results.get(position++); } @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(); } }