Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@
import com.arcadedb.query.sql.executor.Result;
import com.arcadedb.query.sql.executor.ResultInternal;
import com.arcadedb.query.sql.executor.ResultSet;
import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;

public class CypherQueryEngine implements QueryEngine {
private static final String ENGINE_NAME = "cypher-engine";
private final Object arcadeGraph;
private final Object arcadeGraph;

public static class CypherQueryEngineFactory implements QueryEngineFactory {
private static Boolean available = null;
private static Class arcadeGraphClass;
private static Class arcadeCypherClass;
private static Class<?> arcadeGraphClass;
private static Class<?> arcadeCypherClass;

@Override
public boolean isAvailable() {
Expand Down Expand Up @@ -104,47 +108,48 @@ public ResultSet command(final String query, final Map<String, Object> parameter
if (next.isElement())
result.add(next);
else {
Result row;

final Set<String> properties = next.getPropertyNames();

if (properties.size() == 1 && next.getProperty(properties.iterator().next()) instanceof Map) {
// ONLY ONE ELEMENT THAT IS A MAP: EXPAND THE MAP INTO A RECORD
row = mapToResult(next.getProperty(properties.iterator().next()));
// unpack single result projections
Map<String, Object> map = next.toMap();
Object nextValue = map.values().iterator().next();
if (map.size() == 1 && nextValue instanceof Map) {
Map<String, Object> transformed = transformMap((Map<?, ?>) nextValue);
result.add(new ResultInternal(transformed));
} else {
final Map<String, Object> mapStringObject = new HashMap<>(properties.size());
for (String propertyName : properties) {
Object propertyValue = next.getProperty(propertyName);
if (propertyValue instanceof Map)
propertyValue = mapToResult((Map<Object, Object>) propertyValue);
mapStringObject.put(propertyName, propertyValue);
}
row = new ResultInternal(mapStringObject);
Map<String, Object> transformed = transformMap(map);
result.add(new ResultInternal(transformed));
}

result.add(row);
}
}

return result;

} catch (InvocationTargetException e) {
throw new CommandExecutionException("Error on executing cypher command", e.getTargetException());
} catch (Exception e) {
throw new QueryParsingException("Error on executing Cypher query", e);
}
}

private Result mapToResult(Map<Object, Object> map) {
final Map<String, Object> mapStringObject = new HashMap<>(map.size());
private Object transformValue(Object value) {
if (value instanceof Map) {
return new ResultInternal(transformMap((Map<?, ?>) value));
} else if (value instanceof List) {
List<?> listValue = (List<?>) value;
List<Object> transformed = listValue.stream().map(this::transformValue).collect(Collectors.toList());
return transformed.size() == 1 ? transformed.iterator().next() : transformed;
}
return value;
}

private Map<String, Object> transformMap(Map<? extends Object, ? extends Object> map) {

final Map<String, Object> mapStringObject = new HashMap<>(map.size());
Map<Object, Object> internal = new HashMap<>(map);
if (map.containsKey(" cypher.element")) {
mapStringObject.put("@in", map.get(" cypher.inv"));
mapStringObject.put("@out", map.get(" cypher.outv"));
map = (Map<Object, Object>) map.get(" cypher.element");
internal = (Map) map.get(" cypher.element");
}

for (Map.Entry<Object, Object> entry : map.entrySet()) {
for (Map.Entry<Object, Object> entry : internal.entrySet()) {
Object mapKey = entry.getKey();
Object mapValue = entry.getValue();

Expand All @@ -158,14 +163,12 @@ private Result mapToResult(Map<Object, Object> map) {
break;
}
} else if (mapKey.equals(" cypher.element")) {
} else if (mapValue instanceof List && ((List<?>) mapValue).size() == 1) {
mapValue = ((List<?>) mapValue).get(0);
} else {
mapValue = transformValue(mapValue);
}

mapStringObject.put(mapKey.toString(), mapValue);
}

return new ResultInternal(mapStringObject);
return mapStringObject;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.apache.tinkerpop.gremlin.arcadedb;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import org.apache.commons.collections.IteratorUtils;
import org.apache.tinkerpop.gremlin.arcadedb.structure.ArcadeGraph;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.arcadedb.database.Database;
import com.arcadedb.graph.MutableVertex;
import com.arcadedb.query.sql.executor.Result;
import com.arcadedb.query.sql.executor.ResultSet;
import com.arcadedb.schema.Schema;
import com.arcadedb.utility.FileUtils;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CypherQueryEngineTest {

private static final String DB_PATH = "./target/testsql";

@Test
public void verifyProjectionWithCollectFunction() throws ExecutionException, InterruptedException {
final ArcadeGraph graph = ArcadeGraph.open(DB_PATH);
try (Database database = graph.getDatabase()) {
database.transaction(() -> {
Schema schema = database.getSchema();
schema.getOrCreateVertexType("V");
schema.getOrCreateEdgeType("E");

MutableVertex v1 = database.newVertex("V").save();
MutableVertex v2 = database.newVertex("V").save();
MutableVertex v3 = database.newVertex("V").save();

v1.newEdge("E", v2, true);
v1.newEdge("E", v3, true);
try (ResultSet query = database.query("cypher",
"match(parent:V)-[e:E]-(child:V) where id(parent) = $p return parent as parent, collect(child) as childs", "p",
v1.getIdentity())) {

// Ensure that the result (set) has the desired format
List<Result> results = IteratorUtils.toList(query, 1);
assertThat(results, hasSize(1));

Result result = results.get(0);
assertThat(result, notNullValue());
assertThat(result.isProjection(), equalTo(true));
assertThat(result.getPropertyNames(), hasItems("parent", "childs"));

// Transform rid from result to string as in vertex
Result parentAsResult = result.getProperty("parent");
Map<String, Object> parent = parentAsResult.toMap();
parent.computeIfPresent("@rid", (k, v) -> Objects.toString(v));
Map<String, Object> vertexMap = v1.toJSON().toMap();
assertThat(parent, equalTo(vertexMap));

// Transform rid from result to string as in vertex
List<Result> childsAsResult = result.getProperty("childs");
List<Map<String, Object>> childs = childsAsResult.stream()
.map(Result::toMap)
.collect(Collectors.toList());
childs.forEach(c -> c.computeIfPresent("@rid", (k, v) -> Objects.toString(v)));
List<Map<String, Object>> childVertices = Stream.of(v2, v3)
.map(MutableVertex::toJSON)
.map(JSONObject::toMap).collect(Collectors.toList());
assertThat(childs, containsInAnyOrder(childVertices.toArray()));
}

});
} finally {
graph.drop();
}
}

@BeforeEach
@AfterEach
public void clean() {
FileUtils.deleteRecursively(new File(DB_PATH));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,42 +159,40 @@ public void execute(final HttpServerExchange exchange, final ServerSecurityUser
}

private void analyzeResultContent(final Database database, final JsonGraphSerializer serializerImpl, final Set<Identifiable> includedVertices,
final JSONArray vertices, final JSONArray edges, final Result row) {
final JSONArray vertices, final JSONArray edges, final Result row) {
for (String prop : row.getPropertyNames()) {
final Object value = row.getProperty(prop);
if (value instanceof Identifiable) {
final RID rid = ((Identifiable) value).getIdentity();
if (value == null) {
continue;
}
analyzePropertyValue(database, serializerImpl, includedVertices, vertices, edges, value);
}
}

final DocumentType type = database.getSchema().getTypeByBucketId(rid.getBucketId());
if (type instanceof VertexType) {
includedVertices.add((Identifiable) value);
vertices.put(serializerImpl.serializeGraphElement(((Identifiable) value).asVertex(true)));
}
} else if (value instanceof Result) {
analyzeResultContent(database, serializerImpl, includedVertices, vertices, edges, (Result) value);
} else if (value instanceof Collection) {
for (Iterator<?> it = ((Collection<?>) value).iterator(); it.hasNext(); ) {
final Object item = it.next();

if (item instanceof Identifiable) {
final RID rid = ((Identifiable) item).getIdentity();

final DocumentType type = database.getSchema().getTypeByBucketId(rid.getBucketId());
if (type instanceof VertexType) {
includedVertices.add((Identifiable) item);
vertices.put(serializerImpl.serializeGraphElement(((Identifiable) item).asVertex(true)));
} else if (type instanceof EdgeType) {
final Edge edge = ((Identifiable) item).asEdge(true);

edges.put(serializerImpl.serializeGraphElement(edge));

includedVertices.add(edge.getIn());
vertices.put(serializerImpl.serializeGraphElement(edge.getInVertex()));
includedVertices.add(edge.getOut());
vertices.put(serializerImpl.serializeGraphElement(edge.getOutVertex()));
}
}
}
private void analyzePropertyValue(final Database database, final JsonGraphSerializer serializerImpl, final Set<Identifiable> includedVertices,
final JSONArray vertices, final JSONArray edges, final Object value) {
if (value instanceof Identifiable) {
final RID rid = ((Identifiable) value).getIdentity();

final DocumentType type = database.getSchema().getTypeByBucketId(rid.getBucketId());
if (type instanceof VertexType) {
includedVertices.add((Identifiable) value);
vertices.put(serializerImpl.serializeGraphElement(((Identifiable) value).asVertex(true)));
} else if (type instanceof EdgeType) {
final Edge edge = ((Identifiable) value).asEdge(true);

edges.put(serializerImpl.serializeGraphElement(edge));

includedVertices.add(edge.getIn());
vertices.put(serializerImpl.serializeGraphElement(edge.getInVertex()));
includedVertices.add(edge.getOut());
vertices.put(serializerImpl.serializeGraphElement(edge.getOutVertex()));
}
} else if (value instanceof Result) {
analyzeResultContent(database, serializerImpl, includedVertices, vertices, edges, (Result) value);
} else if (value instanceof Collection) {
for (Iterator<?> it = ((Collection<?>) value).iterator(); it.hasNext();) {
analyzePropertyValue(database, serializerImpl, includedVertices, vertices, edges, it.next());
}
}
}
Expand Down