package org.vertexium.cypher.executor; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import org.vertexium.*; import org.vertexium.cypher.VertexiumCypherQueryContext; import org.vertexium.cypher.VertexiumCypherScope; import org.vertexium.cypher.ast.model.*; import org.vertexium.cypher.exceptions.VertexiumCypherException; import org.vertexium.cypher.exceptions.VertexiumCypherNotImplemented; import org.vertexium.cypher.exceptions.VertexiumCypherTypeErrorException; import org.vertexium.cypher.executor.models.match.*; import org.vertexium.cypher.executor.utils.MatchConstraintBuilder; import org.vertexium.cypher.utils.ObjectUtils; import org.vertexium.query.Contains; import org.vertexium.query.Query; import org.vertexium.util.VertexiumLogger; import org.vertexium.util.VertexiumLoggerFactory; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public class MatchClauseExecutor { private static final VertexiumLogger LOGGER = VertexiumLoggerFactory.getLogger(MatchClauseExecutor.class); public VertexiumCypherScope execute(VertexiumCypherQueryContext ctx, List<CypherMatchClause> matchClauses, VertexiumCypherScope scope) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("execute: %s", matchClauses.stream().map(CypherMatchClause::toString).collect(Collectors.joining("; "))); } MatchConstraints matchConstraints = new MatchConstraintBuilder().getMatchConstraints(matchClauses); List<VertexiumCypherScope.Item> results = scope.stream() .flatMap(item -> executeMatchConstraints(ctx, matchConstraints, item).stream()) .collect(Collectors.toList()); return VertexiumCypherScope.newItemsScope(results, scope); } private List<VertexiumCypherScope.Item> executeMatchConstraints( VertexiumCypherQueryContext ctx, MatchConstraints matchConstraints, ExpressionScope scope ) { List<VertexiumCypherScope.Item> results = null; for (PatternPartMatchConstraint patternPartMatchConstraint : matchConstraints.getPatternPartMatchConstraints()) { List<VertexiumCypherScope.Item> patternPartResults = executePatternPartConstraint(ctx, patternPartMatchConstraint, scope); if (results != null) { results = VertexiumCypherScope.Item.cartesianProduct(results, patternPartResults); } else { results = patternPartResults; } } for (CypherAstBase whereExpression : matchConstraints.getWhereExpressions()) { Stream<VertexiumCypherScope.Item> whereResults = ctx.getExpressionExecutor().applyWhereToResults(ctx, results.stream(), whereExpression); results = whereResults.collect(Collectors.toList()); } return results; } public List<VertexiumCypherScope.Item> executePatternPartConstraint( VertexiumCypherQueryContext ctx, PatternPartMatchConstraint patternPartConstraint, ExpressionScope scope ) { List<MatchContext> matchContexts = getInitialMatchContexts(ctx, patternPartConstraint, scope); while (matchContexts.stream().anyMatch(mc -> !mc.isDone())) { matchContexts = resolveMatchContexts(ctx, matchContexts, scope); } Stream<VertexiumCypherScope.Item> results = matchContexts.stream() .map(mc -> mc.toResult(patternPartConstraint.getNamedPaths(), scope)); return results.collect(Collectors.toList()); } private List<MatchContext> getInitialMatchContexts(VertexiumCypherQueryContext ctx, PatternPartMatchConstraint patternPartConstraint, ExpressionScope scope) { List<MatchContext> matchContexts; List<MatchConstraint> foundMatchConstraints = findExistingMatchedMatchConstraintInScope(patternPartConstraint, scope); if (foundMatchConstraints.size() > 0) { matchContexts = getInitialMatchContextsFromFoundItems(patternPartConstraint, foundMatchConstraints, scope); } else { matchContexts = getInitialMatchContextsBySearching(ctx, patternPartConstraint, scope); } return matchContexts; } private List<MatchContext> getInitialMatchContextsBySearching(VertexiumCypherQueryContext ctx, PatternPartMatchConstraint patternPartConstraint, ExpressionScope scope) { List<MatchContext> matchContexts; MatchConstraint workingMatchConstraint = MatchContext.getNextConstraintToWorkOn(patternPartConstraint.getMatchConstraints(), new HashMap<>()); List<? extends Element> matchingElements = executeFirstMatchConstraint(ctx, workingMatchConstraint, scope); if (matchingElements.size() == 0 && workingMatchConstraint.isOptional()) { matchingElements.add(null); } matchContexts = matchingElements.stream() .map(element -> { List<MatchConstraint> remainingMatchConstraints = new ArrayList<>(patternPartConstraint.getMatchConstraints()); remainingMatchConstraints.remove(workingMatchConstraint); return new MatchContext(workingMatchConstraint, element, remainingMatchConstraints); }) .collect(Collectors.toList()); return matchContexts; } private List<MatchContext> getInitialMatchContextsFromFoundItems( PatternPartMatchConstraint patternPartConstraint, List<MatchConstraint> foundMatchConstraints, ExpressionScope scope ) { List<MatchContext> matchContexts = new ArrayList<>(); for (MatchConstraint foundMatchConstraint : foundMatchConstraints) { Object objByName = scope.getByName(foundMatchConstraint.getName()); appendMatchContextsWithFoundItems(matchContexts, patternPartConstraint, foundMatchConstraint, objByName); } return matchContexts; } private void appendMatchContextsWithFoundItems( List<MatchContext> matchContexts, PatternPartMatchConstraint patternPartConstraint, MatchConstraint foundMatchConstraint, Object objByName ) { if (objByName == null || objByName instanceof Element) { Element element = (Element) objByName; if (matchContexts.size() == 0) { List<MatchConstraint> remainingMatchConstraints = new ArrayList<>(patternPartConstraint.getMatchConstraints()); remainingMatchConstraints.remove(foundMatchConstraint); matchContexts.add(new MatchContext(foundMatchConstraint, element, remainingMatchConstraints)); } else { for (MatchContext matchContext : matchContexts) { matchContext.addElement(foundMatchConstraint, element); } } } else if (objByName instanceof List) { for (Object o : ((List<?>) objByName)) { appendMatchContextsWithFoundItems(matchContexts, patternPartConstraint, foundMatchConstraint, o); } } else { throw new VertexiumCypherNotImplemented("non-objects: " + objByName); } } private List<MatchConstraint> findExistingMatchedMatchConstraintInScope(PatternPartMatchConstraint patternPartConstraint, ExpressionScope scope) { List<MatchConstraint> results = new ArrayList<>(); for (MatchConstraint matchConstraint : patternPartConstraint.getMatchConstraints()) { String name = matchConstraint.getName(); if (name != null) { if (scope.contains(name)) { Object obj = scope.getByName(name); if (obj instanceof List && ((List) obj).size() == 0) { continue; } results.add(matchConstraint); } } } return results; } private List<MatchContext> resolveMatchContexts(VertexiumCypherQueryContext ctx, List<MatchContext> matchContexts, ExpressionScope scope) { List<MatchContext> newMatchContexts = new ArrayList<>(); for (MatchContext matchContext : matchContexts) { if (matchContext.isDone()) { newMatchContexts.add(matchContext); } else { newMatchContexts.addAll(resolveMatchContext(ctx, matchContext, scope)); } } return newMatchContexts; } private List<MatchContext> resolveMatchContext(VertexiumCypherQueryContext ctx, MatchContext matchContext, ExpressionScope scope) { MatchConstraint<?, ?> matchConstraint = matchContext.getNextConstraintToWorkOn(); if (matchConstraint == null) { throw new VertexiumCypherException("Cannot solve match clause. Could not find and constraints to work on."); } else if (matchConstraint instanceof NodeMatchConstraint) { return resolveNodeMatchContext(ctx, matchContext, (NodeMatchConstraint) matchConstraint, scope); } else if (matchConstraint instanceof RelationshipMatchConstraint) { return resolveRelationshipMatchContext(ctx, matchContext, (RelationshipMatchConstraint) matchConstraint, scope); } else { throw new VertexiumCypherTypeErrorException(matchConstraint, NodeMatchConstraint.class, RelationshipMatchConstraint.class); } } private List<MatchContext> resolveRelationshipMatchContext( VertexiumCypherQueryContext ctx, MatchContext matchContext, RelationshipMatchConstraint relationshipMatchConstraint, ExpressionScope scope ) { List<Vertex> previousVertices = matchContext.getMatchedVertices(relationshipMatchConstraint); if (previousVertices.size() > 2) { throw new VertexiumCypherNotImplemented("Too many vertices"); } Vertex startingVertex = previousVertices.size() > 0 ? previousVertices.get(0) : null; Vertex endVertex = previousVertices.size() > 1 ? previousVertices.get(1) : null; List<VertexiumCypherScope.PathItem> paths = executeRelationshipConstraint( ctx, startingVertex, endVertex, relationshipMatchConstraint, matchContext, scope ); return paths.stream() .map(path -> MatchContext.concatPath(relationshipMatchConstraint, matchContext, path)) .collect(Collectors.toList()); } private List<MatchContext> resolveNodeMatchContext( VertexiumCypherQueryContext ctx, MatchContext matchContext, NodeMatchConstraint nodeMatchConstraint, ExpressionScope scope ) { List<EdgeVertexConstraint> satisfiedEdgeVertexConstraint = matchContext.getSatisfiedEdgeVertexPairs(ctx, nodeMatchConstraint); if (satisfiedEdgeVertexConstraint.size() == 0) { return new ArrayList<>(); } LinkedHashSet<Vertex> vertices = executeNodeConstraints( ctx, matchContext, satisfiedEdgeVertexConstraint, nodeMatchConstraint, scope ); return vertices.stream() .map(v -> MatchContext.concatVertex(nodeMatchConstraint, matchContext, v)) .collect(Collectors.toList()); } private LinkedHashSet<Vertex> executeNodeConstraints( VertexiumCypherQueryContext ctx, MatchContext matchContext, List<EdgeVertexConstraint> edgeVertexConstraints, NodeMatchConstraint matchConstraint, ExpressionScope scope ) { if (edgeVertexConstraints.size() == 0) { throw new VertexiumCypherException("no edge vertex constraints found"); } List<String> labelNames = getLabelNamesFromMatchConstraint(matchConstraint).stream() .map(ctx::normalizeLabelName) .collect(Collectors.toList()); ListMultimap<String, CypherAstBase> propertiesMap = getPropertiesMapFromElementPatterns(ctx, matchConstraint.getPatterns()); LinkedHashSet<Vertex> results = null; for (EdgeVertexConstraint edgeVertexConstraint : edgeVertexConstraints) { LinkedHashSet<Vertex> newResults = executeNodeConstraint( ctx, matchContext, matchConstraint, edgeVertexConstraint, labelNames, propertiesMap, scope ); if (results == null) { results = newResults; } else { results.addAll(newResults); } } return results; } private LinkedHashSet<Vertex> executeNodeConstraint( VertexiumCypherQueryContext ctx, MatchContext matchContext, NodeMatchConstraint matchConstraint, EdgeVertexConstraint edgeVertexConstraint, List<String> labelNames, ListMultimap<String, CypherAstBase> propertiesMap, ExpressionScope scope ) { LinkedHashSet<Vertex> results = new LinkedHashSet<>(); Edge edge = edgeVertexConstraint.getEdge(); Vertex previousVertex = edgeVertexConstraint.getVertex(); MatchConstraint edgeMatchConstraint = edgeVertexConstraint.getEdgeMatchConstraint(); boolean foundMatch = false; if (edge != null && previousVertex != null) { Vertex vertex = edge.getOtherVertex(previousVertex.getId(), ctx.getFetchHints(), ctx.getAuthorizations()); if (vertexIsMatch(ctx, vertex, labelNames, propertiesMap, scope) && vertexRelationshipMatches(matchContext, matchConstraint, vertex)) { results.add(vertex); foundMatch = true; } } if (!foundMatch && edge == null && previousVertex != null && edgeMatchConstraint.hasZeroRangePattern()) { results.add(previousVertex); foundMatch = true; } if (!foundMatch && matchConstraint.isOptional()) { results.add(null); } return results; } private boolean vertexRelationshipMatches(MatchContext matchContext, NodeMatchConstraint matchConstraint, Vertex vertex) { for (RelationshipMatchConstraint relationshipMatchConstraint : matchConstraint.getConnectedConstraints()) { Object o = matchContext.getResultsByMatchConstraint(relationshipMatchConstraint); if (o != null) { if (o instanceof Edge) { Edge edge = (Edge) o; if (!edge.getVertexId(Direction.OUT).equals(vertex.getId()) && !edge.getVertexId(Direction.IN).equals(vertex.getId())) { return false; } } else if (o instanceof VertexiumCypherScope.PathItem) { VertexiumCypherScope.PathItem pathItem = (VertexiumCypherScope.PathItem) o; if (!pathItem.canVertexConnectOrFoundAtStartOrEnd(vertex)) { return false; } } else { throw new VertexiumCypherException("Unexpected result type: " + o.getClass().getName()); } } } return true; } private boolean vertexIsMatch( VertexiumCypherQueryContext ctx, Vertex vertex, List<String> labelNames, ListMultimap<String, CypherAstBase> propertiesMap, ExpressionScope scope ) { Set<String> vertexLabelNames = ctx.getVertexLabels(vertex); for (String labelName : labelNames) { if (!vertexLabelNames.contains(labelName)) { return false; } } return propertyMapMatch(ctx, vertex, propertiesMap, scope); } private List<? extends Element> executeFirstMatchConstraint( VertexiumCypherQueryContext ctx, MatchConstraint<?, ?> matchConstraint, ExpressionScope scope ) { List<String> labelNames = getLabelNamesFromMatchConstraint(matchConstraint); ListMultimap<String, CypherAstBase> propertiesMap = getPropertiesMapFromElementPatterns(ctx, matchConstraint.getPatterns()); Iterable<? extends Element> elements; if (labelNames.size() == 0 && propertiesMap.size() == 0) { if (matchConstraint instanceof NodeMatchConstraint) { elements = ctx.getGraph().getVertices(ctx.getFetchHints(), ctx.getAuthorizations()); } else if (matchConstraint instanceof RelationshipMatchConstraint) { elements = ctx.getGraph().getEdges(ctx.getFetchHints(), ctx.getAuthorizations()); } else { throw new VertexiumCypherNotImplemented("unexpected constraint type: " + matchConstraint.getClass().getName()); } } else { Query query = ctx.getGraph().query(ctx.getAuthorizations()); if (labelNames.size() > 0) { Stream<String> labelNamesStream = labelNames.stream() .map(ctx::normalizeLabelName); if (matchConstraint instanceof NodeMatchConstraint) { query = labelNamesStream .reduce( query, (q, labelName) -> q.has(ctx.getLabelPropertyName(), labelName), (q, q2) -> q ); } else if (matchConstraint instanceof RelationshipMatchConstraint) { List<String> normalizedLabelNames = labelNamesStream.collect(Collectors.toList()); query = query.hasEdgeLabel(normalizedLabelNames); } else { throw new VertexiumCypherNotImplemented("unexpected constraint type: " + matchConstraint.getClass().getName()); } } for (Map.Entry<String, CypherAstBase> propertyMatch : propertiesMap.entries()) { Object value = ctx.getExpressionExecutor().executeExpression(ctx, propertyMatch.getValue(), scope); if (value instanceof CypherAstBase) { throw new VertexiumException("unexpected value: " + value.getClass().getName() + ": " + value); } if (value instanceof List) { query.has(propertyMatch.getKey(), Contains.IN, value); } else { query.has(propertyMatch.getKey(), value); } } if (matchConstraint instanceof NodeMatchConstraint) { elements = query.vertices(ctx.getFetchHints()); } else if (matchConstraint instanceof RelationshipMatchConstraint) { elements = query.edges(ctx.getFetchHints()); } else { throw new VertexiumCypherNotImplemented("unexpected constraint type: " + matchConstraint.getClass().getName()); } } return Lists.newArrayList(elements); } private List<VertexiumCypherScope.PathItem> executeRelationshipConstraint( VertexiumCypherQueryContext ctx, Vertex startingVertex, Vertex endVertex, RelationshipMatchConstraint matchConstraint, MatchContext matchContext, ExpressionScope scope ) { List<String> labelNames = getRelationshipTypeNamesFromMatchConstraint(matchConstraint).stream() .map(ctx::normalizeLabelName) .collect(Collectors.toList()); ListMultimap<String, CypherAstBase> propertiesMap = getPropertiesMapFromElementPatterns(ctx, matchConstraint.getPatterns()); Direction direction = matchContext.getRelationshipDirection(matchConstraint, startingVertex, endVertex); RelationshipMatchRange range = matchConstraint.getRange(); String name = matchConstraint.getName(); List<VertexiumCypherScope.PathItem> newPaths = new ArrayList<>(); VertexiumCypherScope.PathItem previousPath = VertexiumCypherScope.newEmptyPathItem(null, scope) .setPrintMode(VertexiumCypherScope.PathItem.PrintMode.RELATIONSHIP_RANGE); previousPath = previousPath.concat(null, startingVertex); Vertex vertex = (Vertex) previousPath.getLastElement(); if (range.isRangeSet() && range.isIn(0)) { newPaths.add(previousPath.concat(name, null)); } List<VertexiumCypherScope.PathItem> newPathsToAdd = findPathsToAdd( ctx, previousPath, vertex, endVertex, name, matchConstraint.isOptional(), range, 1, labelNames, propertiesMap, direction, matchContext, scope ); newPaths.addAll(newPathsToAdd); return newPaths; } private List<VertexiumCypherScope.PathItem> findPathsToAdd( VertexiumCypherQueryContext ctx, VertexiumCypherScope.PathItem previousPath, Vertex startingVertex, Vertex endVertex, String name, boolean optional, RelationshipMatchRange range, int depth, List<String> labelNames, ListMultimap<String, CypherAstBase> propertiesMap, Direction direction, MatchContext matchContext, ExpressionScope scope ) { List<VertexiumCypherScope.PathItem> paths = new ArrayList<>(); if (range.isRangeSet() && range.getTo() == null && depth > ctx.getMaxUnboundedRange()) { return paths; } if (startingVertex == null && depth > 1) { return paths; } if (range.isIn(depth)) { boolean foundEdge = false; if (startingVertex != null) { for (Edge edge : startingVertex.getEdges(direction, ctx.getFetchHints(), ctx.getAuthorizations())) { if (previousPath.contains(edge) || matchContext.contains(edge)) { continue; } if (endVertex != null) { if (!edge.getOtherVertexId(startingVertex.getId()).equals(endVertex.getId())) { continue; } } if (edgeIsMatch(ctx, edge, labelNames, propertiesMap, scope)) { paths.add(previousPath.concat(name, edge)); foundEdge = true; } } } if (optional && !foundEdge) { paths.add(previousPath.concat(name, null)); } } if (range.isRangeSet()) { if (startingVertex != null) { for (Edge edge : startingVertex.getEdges(direction, ctx.getFetchHints(), ctx.getAuthorizations())) { if (previousPath.contains(edge) || matchContext.contains(edge)) { continue; } if (edgeIsMatch(ctx, edge, labelNames, propertiesMap, scope)) { Vertex otherVertex = edge.getOtherVertex(startingVertex.getId(), ctx.getFetchHints(), ctx.getAuthorizations()); VertexiumCypherScope.PathItem newPath = previousPath .concat(name, edge) .concat(null, otherVertex); paths.addAll(findPathsToAdd( ctx, newPath, otherVertex, endVertex, name, optional, range, depth + 1, labelNames, propertiesMap, direction, matchContext, scope )); } } } if (optional) { VertexiumCypherScope.PathItem newPath = previousPath .concat(name, null) .concat(null, null); paths.addAll(findPathsToAdd( ctx, newPath, null, endVertex, name, optional, range, depth + 1, labelNames, propertiesMap, direction, matchContext, scope )); } } return paths; } private boolean edgeIsMatch( VertexiumCypherQueryContext ctx, Edge edge, List<String> labelNames, ListMultimap<String, CypherAstBase> propertiesMap, ExpressionScope scope ) { if (labelNames.size() > 0) { if (labelNames.stream().noneMatch(ln -> edge.getLabel().equals(ln))) { return false; } } return propertyMapMatch(ctx, edge, propertiesMap, scope); } private boolean propertyMapMatch( VertexiumCypherQueryContext ctx, Element element, ListMultimap<String, CypherAstBase> propertiesMap, ExpressionScope scope ) { for (Map.Entry<String, CypherAstBase> propertyEntry : propertiesMap.entries()) { Object propertyValue = element.getPropertyValue(propertyEntry.getKey()); Object expressionValue = ctx.getExpressionExecutor().executeExpression(ctx, propertyEntry.getValue(), scope); if (!ObjectUtils.equals(propertyValue, expressionValue)) { return false; } } return true; } private List<String> getRelationshipTypeNamesFromMatchConstraint(RelationshipMatchConstraint matchConstraint) { List<String> results = new ArrayList<>(); for (CypherRelationshipPattern relationshipPattern : matchConstraint.getPatterns()) { if (relationshipPattern.getRelTypeNames() != null) { for (CypherRelTypeName relTypeName : relationshipPattern.getRelTypeNames()) { results.add(relTypeName.getValue()); } } } return results; } private List<String> getLabelNamesFromMatchConstraint(MatchConstraint<?, ?> matchConstraint) { List<String> results = new ArrayList<>(); for (CypherElementPattern pattern : matchConstraint.getPatterns()) { if (pattern instanceof CypherNodePattern) { CypherNodePattern nodePattern = (CypherNodePattern) pattern; if (nodePattern.getLabelNames() != null) { for (CypherLabelName labelName : nodePattern.getLabelNames()) { results.add(labelName.getValue()); } } } else if (pattern instanceof CypherRelationshipPattern) { CypherRelationshipPattern relationshipPattern = (CypherRelationshipPattern) pattern; if (relationshipPattern.getRelTypeNames() != null) { for (CypherRelTypeName relTypeName : relationshipPattern.getRelTypeNames()) { results.add(relTypeName.getValue()); } } } else { throw new VertexiumCypherNotImplemented("unexpected pattern type: " + pattern.getClass().getName()); } } return results; } private <T extends CypherElementPattern> ListMultimap<String, CypherAstBase> getPropertiesMapFromElementPatterns( VertexiumCypherQueryContext ctx, List<T> elementPatterns ) { ListMultimap<String, CypherAstBase> results = ArrayListMultimap.create(); for (CypherElementPattern elementPattern : elementPatterns) { for (Map.Entry<String, CypherAstBase> entry : elementPattern.getPropertiesMap().entrySet()) { results.put(ctx.normalizePropertyName(entry.getKey()), entry.getValue()); } } return results; } private static class EdgeVertexConstraint { private Edge edge; private Vertex vertex; private final MatchConstraint edgeMatchConstraint; public EdgeVertexConstraint(Edge edge, Vertex vertex, MatchConstraint edgeMatchConstraint) { this.edge = edge; this.vertex = vertex; this.edgeMatchConstraint = edgeMatchConstraint; } public Edge getEdge() { return edge; } public Vertex getVertex() { return vertex; } public MatchConstraint getEdgeMatchConstraint() { return edgeMatchConstraint; } } private static class MatchContext { public Map<MatchConstraint, Object> elementsByMatchConstraint = new HashMap<>(); public LinkedHashMap<String, Object> elementsByName = new LinkedHashMap<>(); public List<MatchConstraint> remainingMatchConstraints = new ArrayList<>(); private MatchContext() { } public MatchContext(MatchConstraint matchConstraint, Object element, List<MatchConstraint> remainingMatchConstraints) { this.elementsByMatchConstraint.put(matchConstraint, element); this.remainingMatchConstraints.addAll(remainingMatchConstraints); if (matchConstraint.getName() != null) { this.elementsByName.put(matchConstraint.getName(), element); } } public static MatchContext concatVertex(MatchConstraint matchConstraint, MatchContext previousMatchContext, Vertex vertex) { return concatItem(matchConstraint, previousMatchContext, vertex); } public static MatchContext concatPath(MatchConstraint matchConstraint, MatchContext previousMatchContext, VertexiumCypherScope.PathItem path) { return concatItem(matchConstraint, previousMatchContext, path); } private static MatchContext concatItem(MatchConstraint matchConstraint, MatchContext previousMatchContext, Object o) { MatchContext ctx = new MatchContext(); ctx.elementsByMatchConstraint.put(matchConstraint, o); ctx.remainingMatchConstraints.addAll(previousMatchContext.remainingMatchConstraints); ctx.remainingMatchConstraints.remove(matchConstraint); ctx.elementsByMatchConstraint.putAll(previousMatchContext.elementsByMatchConstraint); ctx.elementsByName.putAll(previousMatchContext.elementsByName); if (matchConstraint.getName() != null) { ctx.elementsByName.put(matchConstraint.getName(), o); } return ctx; } public void addElement(MatchConstraint matchConstraint, Element element) { if (element == null && !matchConstraint.isOptional()) { throw new VertexiumCypherException("element cannot be null"); } elementsByMatchConstraint.put(matchConstraint, element); remainingMatchConstraints.remove(matchConstraint); if (matchConstraint.getName() != null) { elementsByName.put(matchConstraint.getName(), element); } } public boolean isDone() { return remainingMatchConstraints.size() == 0; } public MatchConstraint<?, ?> getNextConstraintToWorkOn() { Map<MatchConstraint, Integer> satisfiedConstraintCount = remainingMatchConstraints.stream() .collect(Collectors.toMap(c -> c, this::getSatisfiedConstraintCount)); return getNextConstraintToWorkOn(remainingMatchConstraints, satisfiedConstraintCount); } private static MatchConstraint getNextConstraintToWorkOn( Collection<MatchConstraint> remainingMatchConstraints, Map<MatchConstraint, Integer> satisfiedConstraintCount ) { return remainingMatchConstraints.stream() .sorted((o1, o2) -> { if (satisfiedConstraintCount.size() > 0) { int o1SatisfiedCount = satisfiedConstraintCount.getOrDefault(o1, 0); int o2SatisfiedCount = satisfiedConstraintCount.getOrDefault(o2, 0); int o1UnsatisfiedCount = o1.getConnectedConstraints().size() - o1SatisfiedCount; int o2UnsatisfiedCount = o2.getConnectedConstraints().size() - o2SatisfiedCount; // pick most satisfied int result = Integer.compare(o1SatisfiedCount, o2SatisfiedCount); if (result != 0) { return -result; } // pick least unsatisfied result = Integer.compare(o1UnsatisfiedCount, o2UnsatisfiedCount); if (result != 0) { return result; } } // pick the non-optional one first if (o1.isOptional() || o2.isOptional()) { if (o1.isOptional() && o2.isOptional()) { return 0; } if (o1.isOptional()) { return 1; } if (o2.isOptional()) { return -1; } } // pick the relationship without a range set if (o1 instanceof RelationshipMatchConstraint && ((RelationshipMatchConstraint) o1).getRange().isRangeSet()) { return 1; } if (o2 instanceof RelationshipMatchConstraint && ((RelationshipMatchConstraint) o2).getRange().isRangeSet()) { return -1; } // find the one most constrained return -Integer.compare(o1.getConstraintCount(), o2.getConstraintCount()); }) .findFirst() .orElse(null); } private int getSatisfiedConstraintCount(MatchConstraint<?, ?> remainingMatchConstraint) { return (int) remainingMatchConstraint.getConnectedConstraints().stream() .filter(c -> elementsByMatchConstraint.containsKey(c)) .count(); } public List<Vertex> getMatchedVertices(RelationshipMatchConstraint matchConstraint) { return matchConstraint.getConnectedConstraints().stream() .map(mc -> (Vertex) elementsByMatchConstraint.get(mc)) .filter(Objects::nonNull) .collect(Collectors.toList()); } public List<EdgeVertexConstraint> getSatisfiedEdgeVertexPairs(VertexiumCypherQueryContext ctx, NodeMatchConstraint matchConstraint) { return matchConstraint.getConnectedConstraints().stream() .flatMap(edgeMatchConstraint -> { Object o = elementsByMatchConstraint.get(edgeMatchConstraint); Edge edge; if (o == null) { return null; } else if (o instanceof Edge) { edge = (Edge) o; } else if (o instanceof VertexiumCypherScope.PathItem) { VertexiumCypherScope.PathItem pathResult = (VertexiumCypherScope.PathItem) o; edge = (Edge) pathResult.getLastElement(); Vertex vertex = (Vertex) pathResult.getElement(-2); return Lists.newArrayList( new EdgeVertexConstraint(edge, vertex, edgeMatchConstraint) ).stream(); } else { throw new VertexiumCypherTypeErrorException(o, Edge.class, VertexiumCypherScope.PathItem.class, null); } List<Vertex> matchedVertices = getMatchedVertices(edgeMatchConstraint); // this can happen if we start the search with relationships if (matchedVertices.size() == 0) { EdgeVertices edgeVertices = edge.getVertices(ctx.getFetchHints(), ctx.getAuthorizations()); switch (edgeMatchConstraint.getDirection()) { case BOTH: case UNSPECIFIED: matchedVertices.add(edgeVertices.getInVertex()); matchedVertices.add(edgeVertices.getOutVertex()); break; case OUT: if (edgeMatchConstraint.isFoundInNext(matchConstraint)) { matchedVertices.add(edgeVertices.getOutVertex()); } else if (edgeMatchConstraint.isFoundInPrevious(matchConstraint)) { matchedVertices.add(edgeVertices.getInVertex()); } else { throw new VertexiumCypherException("unexpected"); } break; case IN: if (edgeMatchConstraint.isFoundInPrevious(matchConstraint)) { matchedVertices.add(edgeVertices.getOutVertex()); } else if (edgeMatchConstraint.isFoundInNext(matchConstraint)) { matchedVertices.add(edgeVertices.getInVertex()); } else { throw new VertexiumCypherException("unexpected"); } break; } } return matchedVertices.stream() .filter(Objects::nonNull) .map(v -> new EdgeVertexConstraint(edge, v, edgeMatchConstraint)); }) .filter(Objects::nonNull) .collect(Collectors.toList()); } public VertexiumCypherScope.Item toResult(Map<String, List<MatchConstraint>> namedPaths, ExpressionScope parentScope) { LinkedHashMap<String, Object> values = new LinkedHashMap<>(); for (Map.Entry<String, Object> entry : elementsByName.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof VertexiumCypherScope.PathItem) { VertexiumCypherScope.PathItem pr = (VertexiumCypherScope.PathItem) value; List<Edge> edges = pr.getEdges(); if (edges.size() == 0) { value = null; } else if (edges.size() == 1) { value = edges.get(0); } } values.put(name, value); } for (Map.Entry<String, List<MatchConstraint>> namedPath : namedPaths.entrySet()) { String pathName = namedPath.getKey(); VertexiumCypherScope.PathItem path = toPathResult(pathName, namedPath.getValue(), parentScope); values.put(pathName, path); } return VertexiumCypherScope.newMapItem(values, parentScope); } private VertexiumCypherScope.PathItem toPathResult(String pathName, List<MatchConstraint> matchConstraints, ExpressionScope parentScope) { VertexiumCypherScope.PathItem result = VertexiumCypherScope.newEmptyPathItem(pathName, parentScope); for (MatchConstraint matchConstraint : matchConstraints) { String elementName = matchConstraint.getName(); Object o = elementsByMatchConstraint.get(matchConstraint); if (o == null) { return null; } else if (o instanceof Element) { result = result.concat(elementName, (Element) o); } else if (o instanceof VertexiumCypherScope.PathItem) { result = result.concat((VertexiumCypherScope.PathItem) o); } else { throw new VertexiumCypherTypeErrorException(o, Element.class, VertexiumCypherScope.PathItem.class, null); } } return result; } public boolean contains(Edge edge) { for (Map.Entry<MatchConstraint, Object> entry : this.elementsByMatchConstraint.entrySet()) { Object o = entry.getValue(); if (o == null) { // not an edge } else if (o instanceof Vertex) { // not an edge } else if (o instanceof Edge) { if (edge.equals(o)) { return true; } } else if (o instanceof VertexiumCypherScope.PathItem) { if (((VertexiumCypherScope.PathItem) o).contains(edge)) { return true; } } else { throw new VertexiumCypherNotImplemented("unknown object type: " + o.getClass().getName()); } } return false; } public Direction getRelationshipDirection(RelationshipMatchConstraint matchConstraint, Vertex startingVertex, Vertex endVertex) { Direction direction = matchConstraint.getDirection().toVertexiumDirection(); if (direction == Direction.BOTH) { return direction; } if (startingVertex == null && endVertex == null) { return null; } if (startingVertex != null) { Direction d = findConstraintsByElement(startingVertex) .map(startingMatchConstraint -> { NodeMatchConstraint nodeMatchConstraint = (NodeMatchConstraint) startingMatchConstraint; if (matchConstraint.isFoundInPrevious(nodeMatchConstraint)) { return direction; } else if (matchConstraint.isFoundInNext(nodeMatchConstraint)) { return direction.reverse(); } else { return null; } }) .filter(Objects::nonNull) .findFirst() .orElse(null); if (d == null) { throw new VertexiumCypherException("Could not find starting match constraint in next or previous"); } return d; } throw new VertexiumCypherNotImplemented("getRelationshipDirection by end vertex"); } private Stream<? extends MatchConstraint> findConstraintsByElement(Element element) { return elementsByMatchConstraint.entrySet().stream() .filter(e -> element.equals(e.getValue())) .map(Map.Entry::getKey); } public Object getResultsByMatchConstraint(RelationshipMatchConstraint relationshipMatchConstraint) { return elementsByMatchConstraint.get(relationshipMatchConstraint); } } }