package org.vertexium.cypher.executor.utils; import com.google.common.collect.Lists; import org.vertexium.cypher.ast.model.*; import org.vertexium.cypher.exceptions.VertexiumCypherTypeErrorException; import org.vertexium.cypher.executor.models.match.*; import java.util.*; import java.util.stream.Collectors; public class MatchConstraintBuilder { public MatchConstraints getMatchConstraints(List<CypherMatchClause> matchClauses) { MatchConstraints matchConstraints = null; for (CypherMatchClause matchClaus : matchClauses) { MatchConstraints newMatchConstraints = matchClauseToConstraints(matchClaus); if (matchConstraints == null) { matchConstraints = newMatchConstraints; } else { matchConstraints = mergeMatchConstraints(matchConstraints, newMatchConstraints); } } LinkedHashSet<PatternPartMatchConstraint> collapseConstraints = collapseConstraints( new LinkedList<>(matchConstraints.getPatternPartMatchConstraints()) ); return new MatchConstraints(collapseConstraints, matchConstraints.getWhereExpressions()); } private MatchConstraints mergeMatchConstraints(MatchConstraints matchConstraintsA, MatchConstraints matchConstraintsB) { LinkedHashSet<PatternPartMatchConstraint> mergedConstraints = new LinkedHashSet<>(); for (PatternPartMatchConstraint patternPartMatchConstraintA : matchConstraintsA.getPatternPartMatchConstraints()) { for (PatternPartMatchConstraint patternPartMatchConstraintB : matchConstraintsB.getPatternPartMatchConstraints()) { if (patternPartMatchConstraintHasOverlap(patternPartMatchConstraintA, patternPartMatchConstraintB)) { mergedConstraints.add(mergePatternPartMatchConstraint(patternPartMatchConstraintA, patternPartMatchConstraintB)); } else { mergedConstraints.add(patternPartMatchConstraintA); mergedConstraints.add(patternPartMatchConstraintB); } } } ArrayList<CypherAstBase> whereExpressions = new ArrayList<>(); whereExpressions.addAll(matchConstraintsA.getWhereExpressions()); whereExpressions.addAll(matchConstraintsB.getWhereExpressions()); return new MatchConstraints(mergedConstraints, whereExpressions); } private LinkedHashSet<PatternPartMatchConstraint> collapseConstraints(LinkedList<PatternPartMatchConstraint> constraints) { LinkedHashSet<PatternPartMatchConstraint> results = new LinkedHashSet<>(); while (constraints.size() > 0) { PatternPartMatchConstraint constraint = constraints.removeFirst(); Optional<PatternPartMatchConstraint> overlappingConstraint = constraints.stream() .filter(c -> patternPartMatchConstraintHasOverlap(constraint, c)) .findFirst(); if (overlappingConstraint.isPresent()) { constraints.remove(overlappingConstraint.get()); constraints.add(mergePatternPartMatchConstraint(constraint, overlappingConstraint.get())); } else { results.add(constraint); } } return results; } private PatternPartMatchConstraint mergePatternPartMatchConstraint( PatternPartMatchConstraint patternPartMatchConstraintA, PatternPartMatchConstraint patternPartMatchConstraintB ) { LinkedHashSet<MatchConstraint> matchConstraints = new LinkedHashSet<>(); matchConstraints.addAll(patternPartMatchConstraintB.getMatchConstraints()); for (MatchConstraint matchConstraintA : patternPartMatchConstraintA.getMatchConstraints()) { boolean foundMatch = false; if (matchConstraintA.getName() != null) { for (MatchConstraint matchConstraintB : patternPartMatchConstraintB.getMatchConstraints()) { if (matchConstraintA.getName().equals(matchConstraintB.getName())) { MatchConstraint.merge(matchConstraintA, matchConstraintB); foundMatch = true; } } } if (!foundMatch) { matchConstraints.add(matchConstraintA); } } Map<String, List<MatchConstraint>> namedMatchConstraints = new HashMap<>(); namedMatchConstraints.putAll(patternPartMatchConstraintA.getNamedPaths()); namedMatchConstraints.putAll(patternPartMatchConstraintB.getNamedPaths()); return new PatternPartMatchConstraint(namedMatchConstraints, matchConstraints); } private boolean patternPartMatchConstraintHasOverlap( PatternPartMatchConstraint patternPartMatchConstraint, PatternPartMatchConstraint newPatternPartMatchConstraint ) { Set<String> partNames = patternPartMatchConstraint.getPartNames(); Set<String> newPartNames = newPatternPartMatchConstraint.getPartNames(); for (String newPartName : newPartNames) { if (partNames.contains(newPartName)) { return true; } } return false; } private MatchConstraints matchClauseToConstraints(CypherMatchClause cypherMatchClause) { LinkedList<PatternPartMatchConstraint> ll = new LinkedList<>( cypherMatchClause.getPatternParts().stream() .map(pp -> patternPartToConstraints(pp, cypherMatchClause.isOptional())) .collect(Collectors.toList()) ); LinkedHashSet<PatternPartMatchConstraint> patternPartMatchConstraints = collapseConstraints(ll); ArrayList<CypherAstBase> whereExpressions = cypherMatchClause.getWhereExpression() == null ? Lists.newArrayList() : Lists.newArrayList(cypherMatchClause.getWhereExpression()); return new MatchConstraints(patternPartMatchConstraints, whereExpressions); } public PatternPartMatchConstraint patternPartToConstraints( CypherPatternPart patternPart, boolean optional ) { String pathName = patternPart.getName(); CypherListLiteral<CypherElementPattern> elementPatterns = patternPart.getElementPatterns(); LinkedHashSet<MatchConstraint> allConstraints = new LinkedHashSet<>(); NodeMatchConstraint firstMatchConstraint = null; MatchConstraint previousConstraint = null; for (CypherElementPattern elementPattern : elementPatterns) { if (elementPattern instanceof CypherNodePattern) { Optional<NodeMatchConstraint> existingNodeMatchConstraint = allConstraints.stream() .filter(c -> c.getName() != null && c.getName().equals(elementPattern.getName())) .map(c -> (NodeMatchConstraint) c) .findFirst(); NodeMatchConstraint nodeMatchConstraint = existingNodeMatchConstraint.orElseGet( () -> new NodeMatchConstraint( elementPattern.getName(), Lists.newArrayList(), optional ) ); nodeMatchConstraint.getPatterns().add((CypherNodePattern) elementPattern); allConstraints.add(nodeMatchConstraint); if (firstMatchConstraint == null) { firstMatchConstraint = nodeMatchConstraint; } if (previousConstraint != null) { //noinspection RedundantCast nodeMatchConstraint.addConnectedConstraint((RelationshipMatchConstraint) previousConstraint); //noinspection RedundantCast ((RelationshipMatchConstraint) previousConstraint).addConnectedConstraint(nodeMatchConstraint); } previousConstraint = nodeMatchConstraint; } else if (elementPattern instanceof CypherRelationshipPattern) { RelationshipMatchConstraint relationshipMatchConstraint = new RelationshipMatchConstraint( elementPattern.getName(), Lists.newArrayList((CypherRelationshipPattern) elementPattern), optional ); allConstraints.add(relationshipMatchConstraint); if (previousConstraint != null) { //noinspection RedundantCast relationshipMatchConstraint.addConnectedConstraint((NodeMatchConstraint) previousConstraint); //noinspection RedundantCast ((NodeMatchConstraint) previousConstraint).addConnectedConstraint(relationshipMatchConstraint); } previousConstraint = relationshipMatchConstraint; } else { throw new VertexiumCypherTypeErrorException(elementPattern, CypherNodePattern.class, CypherRelationshipPattern.class); } } return new PatternPartMatchConstraint(pathName, allConstraints); } }