/************************************************************************ * Copyright (c) 2014-2016 IoT-Solutions e.U. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ************************************************************************/ package iot.jcypher.query.writer; import iot.jcypher.database.DBVersion; import iot.jcypher.domainquery.internal.Settings; import iot.jcypher.query.JcQuery; import iot.jcypher.query.JcQueryParameter; import iot.jcypher.query.api.APIObject; import iot.jcypher.query.api.APIObjectAccess; import iot.jcypher.query.api.IClause; import iot.jcypher.query.ast.ASTNode; import iot.jcypher.query.ast.ClauseType; import iot.jcypher.query.ast.cases.CaseExpression; import iot.jcypher.query.ast.cases.CaseExpression.WhenJcValue; import iot.jcypher.query.ast.collection.CollectExpression; import iot.jcypher.query.ast.collection.CollectExpression.CollectXpressionType; import iot.jcypher.query.ast.collection.CollectionSpec; import iot.jcypher.query.ast.collection.DoEvalExpression; import iot.jcypher.query.ast.collection.EvalExpression; import iot.jcypher.query.ast.collection.ExtractEvalExpression; import iot.jcypher.query.ast.collection.PredicateEvalExpression; import iot.jcypher.query.ast.collection.PropertyEvalExpresssion; import iot.jcypher.query.ast.collection.ReduceEvalExpression; import iot.jcypher.query.ast.index.IndexExpression; import iot.jcypher.query.ast.modify.ModifyExpression; import iot.jcypher.query.ast.modify.ModifyExpression.ModifyAction; import iot.jcypher.query.ast.modify.PropertiesCopy; import iot.jcypher.query.ast.nativ.NativeCypherExpression; import iot.jcypher.query.ast.pattern.PatternElement; import iot.jcypher.query.ast.pattern.PatternExpression; import iot.jcypher.query.ast.pattern.PatternNode; import iot.jcypher.query.ast.pattern.PatternPath; import iot.jcypher.query.ast.pattern.PatternPath.PathFunction; import iot.jcypher.query.ast.pattern.PatternProperty; import iot.jcypher.query.ast.pattern.PatternRelation; import iot.jcypher.query.ast.pattern.PatternRelation.Direction; import iot.jcypher.query.ast.predicate.BooleanOp; import iot.jcypher.query.ast.predicate.BooleanOp.Operator; import iot.jcypher.query.ast.predicate.BooleanValue; import iot.jcypher.query.ast.predicate.ExistsPattern; import iot.jcypher.query.ast.predicate.Predicate; import iot.jcypher.query.ast.predicate.PredicateConcatenator; import iot.jcypher.query.ast.predicate.PredicateExpression; import iot.jcypher.query.ast.predicate.PredicateFunction; import iot.jcypher.query.ast.predicate.PredicateFunction.PredicateFunctionType; import iot.jcypher.query.ast.predicate.SubExpression; import iot.jcypher.query.ast.returns.Order; import iot.jcypher.query.ast.returns.ReturnAggregate; import iot.jcypher.query.ast.returns.ReturnAggregate.AggregateFunctionType; import iot.jcypher.query.ast.returns.ReturnBoolean; import iot.jcypher.query.ast.returns.ReturnCollection; import iot.jcypher.query.ast.returns.ReturnElement; import iot.jcypher.query.ast.returns.ReturnExpression; import iot.jcypher.query.ast.returns.ReturnPattern; import iot.jcypher.query.ast.returns.ReturnValue; import iot.jcypher.query.ast.start.PropertyOrQuery; import iot.jcypher.query.ast.start.StartExpression; import iot.jcypher.query.ast.union.UnionExpression; import iot.jcypher.query.ast.using.UsingExpression; import iot.jcypher.query.values.JcElement; import iot.jcypher.query.values.JcLabel; import iot.jcypher.query.values.JcNode; import iot.jcypher.query.values.JcProperty; import iot.jcypher.query.values.JcValue; import iot.jcypher.query.values.ValueAccess; import iot.jcypher.query.values.ValueElement; import iot.jcypher.query.values.ValueWriter; import java.util.Collection; import java.util.Iterator; import java.util.List; public class CypherWriter { // currently if you use parameters in a cypher query, and a parameter is a list, // this does not work properly. Instead of storing a list as property value, // everything is converted to a string. // The workaround is not to use paramaters with cypher queries and lists. // This is true for NEO4J Versions at least up to 2.1.4 // If this is corrected in NEO4J some time to come, you simply in class CypherWriter // can set CORRECT_FOR_LIST_WITH_PARAMS to false and the workaround // will be removed private static final boolean CORRECT_FOR_LIST_WITH_PARAMS = true; private static final String dBVersion_21x = "2.1.x"; // clauses which can't use parameter sets private static final ClauseType[] dontUseParamSet = new ClauseType[] { ClauseType.MERGE }; private static boolean inDontUseParamSet(ClauseType ct) { for (ClauseType t : dontUseParamSet) { if (ct == t) return true; } return false; } public static void toCypherExpression(JcQuery query, WriterContext context) { if (!DBVersion.Neo4j_Version.equals(dBVersion_21x) && Settings.writeRulePlanner.get()) { context.buffer.append("CYPHER planner="); context.buffer.append(Settings.plannerStrategy.name().toLowerCase()); Pretty.writePreClauseSeparator(context, context.buffer); } toCypherExpression(query.getClauses(), 0, context); } public static void toCypherExpression(IClause[] clauses, int index, WriterContext context) { int idx = index; for (IClause clause : clauses) { CypherWriter.toCypherExpression(clause, idx, context); idx++; } addFilterExpressionsIfNeeded(context, true); } public static void toCypherExpression(IClause clause, int index, WriterContext context) { toCypherExpression(APIObjectAccess.getAstNode((APIObject) clause), index, context); } private static void toCypherExpression(ASTNode astNode, int index, WriterContext context) { boolean hasStart = index > 0; ClauseType clauseType = astNode.getClauseType(); context.currentClause = clauseType; addFilterExpressionsIfNeeded(context, false); /*** CYPHER NATIVE CLAUSE **************************************/ if (clauseType == ClauseType.CYPHER_NATIVE) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); NativeCypherWriter.toCypherExpression((NativeCypherExpression)astNode, context); } /*** START CLAUSE **************************************/ else if (clauseType == ClauseType.START) { if (context.previousClause != ClauseType.START) { // otherwise concat multiple starts if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("START"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } StartCypherWriter.toCypherExpression((StartExpression)astNode, context); } /*** UNION CLAUSE, UNION ALL CLAUSE **************************************/ else if (clauseType == ClauseType.UNION || clauseType == ClauseType.UNION_ALL) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); UnionExpression ux = (UnionExpression)astNode; if (ux.isDistinct()) context.buffer.append("UNION"); else context.buffer.append("UNION ALL"); Pretty.writePostClauseSeparator(context, context.buffer); } /*** WITH CLAUSE **************************************/ else if (clauseType == ClauseType.WITH) { if (context.previousClause != ClauseType.WITH) { // otherwise concat multiple withs if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("WITH"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } ReturnCypherWriter.toCypherExpression((ReturnExpression)astNode, context); } /*** MATCH CLAUSE **************************************/ else if (clauseType == ClauseType.MATCH) { if (context.previousClause != ClauseType.MATCH) { // otherwise concat multiple matches if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("MATCH"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } PatternCypherWriter.toCypherExpression((PatternExpression)astNode, context); } /*** OPTIONAL MATCH CLAUSE **************************************/ else if (clauseType == ClauseType.OPTIONAL_MATCH) { if (context.previousClause != ClauseType.OPTIONAL_MATCH) { // otherwise concat multiple matches if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("OPTIONAL MATCH"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } PatternCypherWriter.toCypherExpression((PatternExpression)astNode, context); } /*** USING INDEX CLAUSE **************************************/ else if (clauseType == ClauseType.USING_INDEX) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("USING INDEX"); Pretty.writePostClauseSeparator(context, context.buffer); UCypherWriter.toCypherExpression((UsingExpression)astNode, context); } /*** USING SCAN CLAUSE **************************************/ else if (clauseType == ClauseType.USING_SCAN) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("USING SCAN"); Pretty.writePostClauseSeparator(context, context.buffer); UCypherWriter.toCypherExpression((UsingExpression)astNode, context); } /*** WHERE CLAUSE **************************************/ else if (clauseType == ClauseType.WHERE) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("WHERE"); Pretty.writePostClauseSeparator(context, context.buffer); PredicateCypherWriter.toCypherExpression((PredicateExpression)astNode, context); } /*** CREATE CLAUSE **************************************/ else if (clauseType == ClauseType.CREATE) { if (context.previousClause != ClauseType.CREATE) { // otherwise concat multiple creates if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("CREATE"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } PatternCypherWriter.toCypherExpression((PatternExpression)astNode, context); } /*** CREATE UNIQUE CLAUSE **************************************/ else if (clauseType == ClauseType.CREATE_UNIQUE) { if (context.previousClause != ClauseType.CREATE_UNIQUE) { // otherwise concat multiple creates if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("CREATE UNIQUE"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } PatternCypherWriter.toCypherExpression((PatternExpression)astNode, context); } /*** MERGE CLAUSE **************************************/ else if (clauseType == ClauseType.MERGE) { // never concatenate MERGE clauses if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("MERGE"); Pretty.writePostClauseSeparator(context, context.buffer); PatternCypherWriter.toCypherExpression((PatternExpression)astNode, context); } /*** RETURN CLAUSE **************************************/ else if (clauseType == ClauseType.RETURN) { if (context.previousClause != ClauseType.RETURN) { // otherwise concat multiple returns if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("RETURN"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } ReturnCypherWriter.toCypherExpression((ReturnExpression)astNode, context); } /*** SET CLAUSE **************************************/ else if (clauseType == ClauseType.SET) { if (context.previousClause != ClauseType.SET) { // otherwise concat multiple removes if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("SET"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } STCypherWriter.toCypherExpression((ModifyExpression)astNode, context); } /*** DELETE CLAUSE **************************************/ else if (clauseType == ClauseType.DELETE) { if (context.previousClause != ClauseType.DELETE) { // otherwise concat multiple deletes if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("DELETE"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } STCypherWriter.toCypherExpression((ModifyExpression)astNode, context); } /*** DETACH DELETE CLAUSE **************************************/ else if (clauseType == ClauseType.DETACH_DELETE) { if (context.previousClause != ClauseType.DETACH_DELETE) { // otherwise concat multiple deletes if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("DETACH DELETE"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } STCypherWriter.toCypherExpression((ModifyExpression)astNode, context); } /*** REMOVE CLAUSE **************************************/ else if (clauseType == ClauseType.REMOVE) { if (context.previousClause != ClauseType.REMOVE) { // otherwise concat multiple removes if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("REMOVE"); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } STCypherWriter.toCypherExpression((ModifyExpression)astNode, context); } /*** FOREACH CLAUSE **************************************/ else if (clauseType == ClauseType.FOREACH) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("FOREACH"); Pretty.writePostClauseSeparator(context, context.buffer); CollectionCypherWriter.toCypherSubExpression((CollectExpression)astNode, context); } /*** CREATE INDEX CLAUSE **************************************/ else if (clauseType == ClauseType.CREATE_INDEX) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("CREATE INDEX ON"); Pretty.writePostClauseSeparator(context, context.buffer); IndexCypherWriter.toCypherExpression((IndexExpression)astNode, context); } /*** DROP INDEX CLAUSE **************************************/ else if (clauseType == ClauseType.DROP_INDEX) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("DROP INDEX ON"); Pretty.writePostClauseSeparator(context, context.buffer); IndexCypherWriter.toCypherExpression((IndexExpression)astNode, context); } /*** CASE CLAUSE **************************************/ else if (clauseType == ClauseType.CASE) { if (context.previousClause == ClauseType.RETURN) context.buffer.append(','); if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("CASE"); Pretty.writePostClauseSeparator(context, context.buffer); CaseCypherWriter.toCaseExpression((CaseExpression)astNode, context); } /*** WHEN CLAUSE **************************************/ else if (clauseType == ClauseType.WHEN) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append("WHEN"); Pretty.writePostClauseSeparator(context, context.buffer); CaseCypherWriter.toWhenExpression((PredicateExpression)astNode, context); Pretty.writeStatementSeparator(context, context.buffer); context.buffer.append("THEN"); } /*** ELSE CLAUSE **************************************/ else if (clauseType == ClauseType.ELSE) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append(clauseType.name()); //Pretty.writePostClauseSeparator(context, context.buffer); } /*** END CLAUSE **************************************/ else if (clauseType == ClauseType.END) { if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append(clauseType.name()); Pretty.writePostClauseSeparator(context, context.buffer); CaseCypherWriter.toEndExpression((CaseExpression)astNode, context); } /*** ON_CREATE, ON_MATCH CLAUSEs **************************************/ else if (clauseType != null && clauseType.getDisplay() != null) { if (context.previousClause != clauseType) { // otherwise concat if (hasStart) Pretty.writePreClauseSeparator(context, context.buffer); context.buffer.append(clauseType.getDisplay()); Pretty.writePostClauseSeparator(context, context.buffer); } else { context.buffer.append(','); Pretty.writeStatementSeparator(context, context.buffer); } STCypherWriter.toCypherExpression((ModifyExpression)astNode, context); } context.previousClause = context.currentClause; } private static void addFilterExpressionsIfNeeded(WriterContext context, boolean end) { if (context.filterBuffer != null) { if ((context.currentClause != ClauseType.WITH && context.previousClause == ClauseType.WITH) || (context.currentClause != ClauseType.RETURN && context.previousClause == ClauseType.RETURN) || end) { context.buffer.append(context.filterBuffer); context.filterBuffer = null; } } } /*****************************************************/ private static class CollectionCypherWriter { private static void toCypherExpression(CollectExpression collectExpression, WriterContext context) { //Pretty.writeStatementSeparator(context, context.buffer); toCypherExpressionRecursive(collectExpression, context); } private static void toCypherExpression(PredicateFunction pf, WriterContext context) { context.incrementLevel(); Pretty.writePreFunctionSeparator(context); if (pf.getType() == PredicateFunctionType.ALL) context.buffer.append("ALL("); else if (pf.getType() == PredicateFunctionType.ANY) context.buffer.append("ANY("); else if (pf.getType() == PredicateFunctionType.NONE) context.buffer.append("NONE("); else if (pf.getType() == PredicateFunctionType.SINGLE) context.buffer.append("SINGLE("); CollectExpression collXpr = pf.getCollectExpression(); toCypherExpressionRecursive(collXpr, context); context.decrementLevel(); context.buffer.append(')'); } private static void toCypherSubExpression(CollectExpression collXpr, WriterContext context) { context.buffer.append('('); toCypherExpression(collXpr, context); context.buffer.append(')'); } private static void toCypherExpression(CollectionSpec collSpec, WriterContext context) { if (collSpec != null) { if (collSpec.getCollection() != null) { toCypherExpressionRecursive(collSpec.getCollection(), context); } else if (collSpec.getJcCollection() != null) { ValueWriter.toValueExpression(collSpec.getJcCollection(), context, context.buffer); } else if (collSpec.getCollectionValues() != null) { context.buffer.append('['); int idx = 0; for (Object val : collSpec.getCollectionValues()) { if (idx > 0) context.buffer.append(", "); PrimitiveCypherWriter.writePrimitiveValue(val, context, context.buffer); idx++; } context.buffer.append(']'); } } } private static void toCypherExpressionRecursive(CollectExpression collectExpression, WriterContext context) { boolean closeBracket = true; if (collectExpression.getType() == CollectXpressionType.EXTRACT) context.buffer.append("extract("); else if (collectExpression.getType() == CollectXpressionType.FILTER) context.buffer.append("filter("); else if (collectExpression.getType() == CollectXpressionType.TAIL) context.buffer.append("tail("); else if (collectExpression.getType() == CollectXpressionType.NODES) context.buffer.append("nodes("); else if (collectExpression.getType() == CollectXpressionType.RELATIONS) context.buffer.append("relationships("); else if (collectExpression.getType() == CollectXpressionType.LABELS) context.buffer.append("labels("); else if (collectExpression.getType() == CollectXpressionType.COLLECT) context.buffer.append("collect("); else if (collectExpression.getType() == CollectXpressionType.REDUCE) context.buffer.append("reduce("); else closeBracket = false; if (collectExpression.getEvalExpression() != null) toCypherExpression(collectExpression.getEvalExpression(), collectExpression.getIterationVariable(), true, context); if (collectExpression.getIterationVariable() != null) { ValueWriter.toValueExpression(collectExpression.getIterationVariable(), context, context.buffer); context.buffer.append(" IN "); } if (collectExpression.getType() == CollectXpressionType.CREATE && collectExpression.getNestedClauses() != null) { CollectionCypherWriter.writeInnerClauses(collectExpression.getNestedClauses(), context); } CollectionSpec collSpec = collectExpression.getCollectionToOperateOn(); toCypherExpression(collSpec, context); if (collectExpression.getType() == CollectXpressionType.EXTRACT || collectExpression.getType() == CollectXpressionType.FOREACH || collectExpression.getType() == CollectXpressionType.REDUCE) context.buffer.append(" | "); else if (collectExpression.getType() == CollectXpressionType.FILTER || collectExpression.getType() == CollectXpressionType.PREDICATE_FUNCTION) context.buffer.append(" WHERE "); if (collectExpression.getNestedClauses() != null && collectExpression.getType() == CollectXpressionType.FOREACH) { CollectionCypherWriter.writeInnerClauses(collectExpression.getNestedClauses(), context); } if (collectExpression.getEvalExpression() != null) toCypherExpression(collectExpression.getEvalExpression(), collectExpression.getIterationVariable(), false, context); if (closeBracket) context.buffer.append(')'); } private static void toCypherExpression(EvalExpression evalExpression, JcValue jcValue, boolean preCollectionSpec, WriterContext context) { if (evalExpression instanceof PropertyEvalExpresssion && !preCollectionSpec) { PropertyEvalExpresssion propEval = (PropertyEvalExpresssion)evalExpression; if (jcValue != null) ValueWriter.toValueExpression(jcValue, context, context.buffer); context.buffer.append('.'); context.buffer.append(propEval.getPropertyName()); } else if (evalExpression instanceof PredicateEvalExpression && !preCollectionSpec) { PredicateEvalExpression pEval = (PredicateEvalExpression)evalExpression; PredicateCypherWriter.toCypherExpression(pEval.getPredicateExpression(), context); } else if (evalExpression instanceof DoEvalExpression && !preCollectionSpec) { DoEvalExpression doEval = (DoEvalExpression)evalExpression; toCypherExpression(doEval, context); } else if (evalExpression instanceof ReduceEvalExpression && preCollectionSpec) { ReduceEvalExpression reduceEval = (ReduceEvalExpression)evalExpression; ValueWriter.toValueExpression(reduceEval.getResultVariable(), context, context.buffer); context.buffer.append(" = "); PredicateCypherWriter.toCypherExpression(reduceEval.getInitialValue(), context); context.buffer.append(", "); } else if (evalExpression instanceof ReduceEvalExpression && !preCollectionSpec) { ReduceEvalExpression reduceEval = (ReduceEvalExpression)evalExpression; ValueWriter.toValueExpression(reduceEval.getReduceExpression(), context, context.buffer); } else if (evalExpression instanceof ExtractEvalExpression && !preCollectionSpec) { ExtractEvalExpression extractEval = (ExtractEvalExpression)evalExpression; ValueWriter.toValueExpression(extractEval.getExpression(), context, context.buffer); } } private static void toCypherExpression(DoEvalExpression doEval, WriterContext context) { int idx = 0; boolean inF = context.inFunction; context.inFunction = true; for (ASTNode astNode : doEval.getClauses()) { CypherWriter.toCypherExpression(astNode, idx, context); idx++; } context.inFunction = inF; } private static void writeInnerClauses(IClause[] innerClauses, WriterContext context) { Format orgFormat = context.cypherFormat; ClauseType curClause = context.currentClause; //ClauseType prevClause = context.previousClause; context.cypherFormat = Format.NONE; context.previousClause = null; context.currentClause = null; CypherWriter.toCypherExpression(innerClauses, 0, context); context.cypherFormat = orgFormat; //context.previousClause = prevClause; context.previousClause = null; // to avoid unwanted concatenation context.currentClause = curClause; } } /*****************************************************/ private static class PredicateCypherWriter { private static void toCypherExpression(PredicateExpression pxpr, WriterContext context) { PredicateConcatenator concat; Predicate predicate = pxpr.getPredicate(); if (predicate != null) { PredicateCypherWriter.toCypherExpression(predicate, context); while (predicate != null && (concat = predicate.getNext()) != null) { PredicateCypherWriter.toCypherExpression(concat, context); predicate = concat.getPredicate(); } } } private static void toCypherExpression(PredicateConcatenator concat, WriterContext context) { context.buffer.append(' '); context.buffer.append(concat.getConcatOperator().name()); context.buffer.append(' '); PredicateCypherWriter.toCypherExpression(concat.getPredicate(), context); } private static void toCypherExpression(Predicate pred, WriterContext context) { for (int i = 0; i< pred.getNotCount();i++) context.buffer.append("NOT "); if (pred instanceof SubExpression) { context.buffer.append('('); PredicateCypherWriter.toCypherExpression(((SubExpression)pred).getPredicateExpression(), context); context.buffer.append(')'); } else if (pred instanceof ExistsPattern) { PatternCypherWriter.toCypherExpression(((ExistsPattern)pred).getPatternExpression(), context); } else if (pred instanceof BooleanOp) { PredicateCypherWriter.toCypherExpression((BooleanOp)pred, context); } else if (pred instanceof PredicateFunction) { PredicateFunction pf = (PredicateFunction)pred; CollectionCypherWriter.toCypherExpression(pf, context); } else if (pred instanceof BooleanValue) { PrimitiveCypherWriter.writePrimitiveValue(((BooleanValue)pred).isTRUE() ? Boolean.TRUE : Boolean.FALSE, context, context.buffer); } } private static void toCypherExpression(BooleanOp boolOp, WriterContext context) { boolean hasLabel = boolOp.getOperator() == Operator.HAS && boolOp.getOperand1() instanceof JcLabel; boolean hasProperty = boolOp.getOperator() == Operator.HAS && boolOp.getOperand1() instanceof JcProperty; boolean isEmptyWhenValue = boolOp.getOperand1() instanceof WhenJcValue; if (hasProperty) context.buffer.append("HAS("); if (boolOp.getOperand1() != null) { ValueWriter.toValueExpression(boolOp.getOperand1(), context, context.buffer); } if (!hasLabel && !hasProperty) { if (!isEmptyWhenValue) { context.buffer.append(' '); if (boolOp.getOperator() == Operator.IN) { CollectionSpec collSpec = (CollectionSpec) boolOp.getOperand2(); context.buffer.append("IN"); if (collSpec.getCollectionValues() == null) context.buffer.append(' '); CollectionCypherWriter.toCypherExpression(collSpec, context); } else if (boolOp.getOperator() == Operator.IS_NULL) { context.buffer.append("IS NULL"); } else { context.buffer.append(PredicateCypherWriter.getOperatorSymbol(boolOp.getOperator())); context.buffer.append(' '); PredicateCypherWriter.toCypherExpression(boolOp.getOperand2(), context); } } else PredicateCypherWriter.toCypherExpression(boolOp.getOperand2(), context); } else { if (hasProperty) context.buffer.append(')'); } } private static String getOperatorSymbol(Operator operator) { if (operator == Operator.EQUALS) return "="; else if (operator == Operator.NOT_EQUALS) return "<>"; else if (operator == Operator.LT) return "<"; else if (operator == Operator.LTE) return "<="; else if (operator == Operator.GT) return ">"; else if (operator == Operator.GTE) return ">="; else if (operator == Operator.REGEX) return "=~"; else if (operator == Operator.STARTS_WITH) return "STARTS WITH"; else if (operator == Operator.ENDS_WITH) return "ENDS WITH"; else if (operator == Operator.CONTAINS) return "CONTAINS"; throw new RuntimeException("Operator: " + operator + " not yet implemented"); } private static void toCypherExpression(Object valueElement_Or_PrimitiveValue, WriterContext context) { if (valueElement_Or_PrimitiveValue instanceof ValueElement) ValueWriter.toValueExpression((ValueElement)valueElement_Or_PrimitiveValue, context, context.buffer); else if (valueElement_Or_PrimitiveValue != null) { if (QueryParam.isExtractParams(context)) { QueryParam qp = QueryParam.createAddParam(null, valueElement_Or_PrimitiveValue, context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else PrimitiveCypherWriter.writePrimitiveValue(valueElement_Or_PrimitiveValue, context, context.buffer); } } } /*****************************************************/ private static class ReturnCypherWriter { private static void toCypherExpression(ReturnExpression rx, WriterContext context) { boolean closeBracket = false; if (rx.isCount()) { context.buffer.append("count("); closeBracket = true; } if (rx.isDistinct()) context.buffer.append("DISTINCT "); ReturnValue re = rx.getReturnValue(); if (re instanceof ReturnElement) { if (((ReturnElement)re).isAll()) context.buffer.append("*"); else { JcValue jcVal = ((ReturnElement)re).getElement(); ValueWriter.toValueExpression(jcVal, context, context.buffer); } } else if (re instanceof ReturnBoolean) { PredicateCypherWriter.toCypherExpression(((ReturnBoolean)re).getPredicateExpression(), context); } else if (re instanceof ReturnPattern) { PatternCypherWriter.toCypherExpression(((ReturnPattern)re).getPatternExpression(), context); } else if (re instanceof ReturnCollection) { CollectExpression cx = ((ReturnCollection)re).getCollectExpression(); CollectionCypherWriter.toCypherExpression(cx, context); } else if (re instanceof ReturnAggregate) { ReturnAggregate ra = (ReturnAggregate)re; toCypherExpression(ra, context); } if (closeBracket) context.buffer.append(')'); if (rx.getAlias() != null) { context.buffer.append(" AS "); ValueWriter.toValueExpression(rx.getAlias(), context, context.buffer); } writeFilterExpressions(rx, context); } private static void toCypherExpression(ReturnAggregate ra, WriterContext context) { AggregateFunctionType type = ra.getType(); if (type == AggregateFunctionType.SUM) context.buffer.append("sum("); else if (type == AggregateFunctionType.AVG) context.buffer.append("avg("); else if (type == AggregateFunctionType.PERCENTILE_DISC) context.buffer.append("percentileDisc("); else if (type == AggregateFunctionType.PERCENTILE_CONT) context.buffer.append("percentileCont("); else if (type == AggregateFunctionType.STDEV) context.buffer.append("stdev("); else if (type == AggregateFunctionType.STDEVP) context.buffer.append("stdevp("); else if (type == AggregateFunctionType.MAX) context.buffer.append("max("); else if (type == AggregateFunctionType.MIN) context.buffer.append("min("); if (ra.isDistinct()) context.buffer.append("DISTINCT "); ValueWriter.toValueExpression(ra.getArgument(), context, context.buffer); if (type == AggregateFunctionType.PERCENTILE_DISC) { context.buffer.append(", "); context.buffer.append(ra.getPercentile()); } else if (type == AggregateFunctionType.PERCENTILE_CONT) { context.buffer.append(", "); context.buffer.append(ra.getPercentile()); } context.buffer.append(')'); } private static void writeFilterExpressions(ReturnExpression rx, WriterContext context) { if (hasOrderExpressions(rx)) { int idx = 0; if (context.filterBuffer == null) { context.filterBuffer = new StringBuilder(); Pretty.writePreClauseSeparator(context, context.filterBuffer); context.filterBuffer.append("ORDER BY"); Pretty.writePostClauseSeparator(context, context.filterBuffer); } else idx++; for (Order order : rx.getOrders()) { if (idx > 0) { context.filterBuffer.append(','); Pretty.writeStatementSeparator(context, context.filterBuffer); } JcValue elem = ((ReturnElement)rx.getReturnValue()).getElement(); ValueWriter.toValueExpression( ValueAccess.findFirst(elem), context, context.filterBuffer); context.filterBuffer.append('.'); context.filterBuffer.append(order.getPropertyName()); if (order.isDescending()) context.filterBuffer.append(" DESC"); idx++; } } int skip = rx.getSkip(); if (skip != -1) { if (context.filterBuffer == null) context.filterBuffer = new StringBuilder(); Pretty.writePreClauseSeparator(context, context.filterBuffer); context.filterBuffer.append("SKIP "); if (QueryParam.isExtractParams(context)) { QueryParam qp = QueryParam.createAddParam(null, skip, context); PrimitiveCypherWriter.writeParameter(qp, context.filterBuffer); } else context.filterBuffer.append(skip); } int limit = rx.getLimit(); if (limit != -1) { if (context.filterBuffer == null) context.filterBuffer = new StringBuilder(); Pretty.writePreClauseSeparator(context, context.filterBuffer); context.filterBuffer.append("LIMIT "); if (QueryParam.isExtractParams(context)) { QueryParam qp = QueryParam.createAddParam(null, limit, context); PrimitiveCypherWriter.writeParameter(qp, context.filterBuffer); } else context.filterBuffer.append(limit); } } private static boolean hasOrderExpressions(ReturnExpression rx) { return rx.getOrders() != null && rx.getOrders().size() > 0; } } /*****************************************************/ private static class IndexCypherWriter { private static void toCypherExpression(IndexExpression ix, WriterContext context) { context.buffer.append(':'); context.buffer.append(ix.getLabelName()); context.buffer.append('('); context.buffer.append(ix.getPropertyName()); context.buffer.append(')'); } } /*****************************************************/ private static class STCypherWriter { private static void toCypherExpression(ModifyExpression mx, WriterContext context) { if (mx.getModifyAction() == ModifyAction.SET || mx.getModifyAction() == ModifyAction.REMOVE) { if (mx.getToModify() != null) { ValueWriter.toValueExpression(mx.getToModify(), context, context.buffer); if (mx.getModifyAction() == ModifyAction.SET) context.buffer.append(" = "); if (mx.getValue() != null) { // only in case of SET if (QueryParam.isExtractParams(context) && (!CORRECT_FOR_LIST_WITH_PARAMS || !(mx.getValue() instanceof List<?>))) { QueryParam qp = QueryParam.createAddParam(null, mx.getValue(), context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else PrimitiveCypherWriter.writePrimitiveValue(mx.getValue(), context, context.buffer); } else if (mx.getValueExpression() != null) ValueWriter.toValueExpression(mx.getValueExpression(), context, context.buffer); else if (mx.isToNull()) context.buffer.append("NULL"); } else if (mx.getPropertiesCopy() != null) { PropertiesCopy pc = mx.getPropertiesCopy(); ValueWriter.toValueExpression(pc.getTarget(), context, context.buffer); context.buffer.append(" = "); ValueWriter.toValueExpression(pc.getSource(), context, context.buffer); } else if (mx.getModifyLabels() != null) { ValueWriter.toValueExpression(mx.getModifyLabels().getTargetNode(), context, context.buffer); for (String label : mx.getModifyLabels().getLabels()) { context.buffer.append(':'); context.buffer.append(label); } } } else if (mx.getModifyAction() == ModifyAction.DELETE) { ValueWriter.toValueExpression(mx.getElementToDelete(), context, context.buffer); } } } /*****************************************************/ private static class NativeCypherWriter { private static void toCypherExpression(NativeCypherExpression ncxpr, WriterContext context) { int idx = 0; for(String line : ncxpr.getLines()) { if (idx > 0) context.buffer.append("\n"); context.buffer.append(line); idx++; } } } /*****************************************************/ private static class PatternCypherWriter { private static void toCypherExpression(PatternExpression xpr, WriterContext context) { boolean bracketClose = false; if (xpr.getPath() != null) { bracketClose = PatternCypherWriter.toCypherExpression(xpr.getPath(), context); } for (PatternElement elem : xpr.getElements()) { PatternCypherWriter.toCypherExpression(elem, context); } if (bracketClose) context.buffer.append(')'); } /** * @param path * @param context * @return true, if a closing bracket should be written */ private static boolean toCypherExpression(PatternPath path, WriterContext context) { boolean bracketClose = true; ValueWriter.toValueExpression(path.getJcPath(), context, context.buffer); context.buffer.append(" = "); if (path.getPathFunction() == PathFunction.PATH) { bracketClose = false; } else if (path.getPathFunction() == PathFunction.SHORTEST_PATH) { context.buffer.append("shortestPath("); } else if (path.getPathFunction() == PathFunction.ALL_SHORTEST_PATHS) { context.buffer.append("allShortestPaths("); } return bracketClose; } private static void toCypherExpression(PatternProperty property, WriterContext context) { context.buffer.append(property.getName()); context.buffer.append(':'); if (property.getValue() instanceof ValueElement) { QueryParamSet.disableUseSet(context); context.buffer.append(' '); ValueWriter.toValueExpression((ValueElement)property.getValue(), context, context.buffer); } else if (property.getValue() != null) { // TODO remove later (test with future versions of Neo4J) // workaround for bug: using lists with query parameters // they are mapped to strings instead of being stored as lists (Neo4J Version 2.1.4) boolean disableParamSet = property.getValue() instanceof List<?> && CORRECT_FOR_LIST_WITH_PARAMS; if (disableParamSet) QueryParamSet.disableUseSet(context); if (QueryParam.isExtractParams(context) && (!CORRECT_FOR_LIST_WITH_PARAMS || !(property.getValue() instanceof List<?>))) { QueryParam qp = QueryParam.createParam(property.getName(), property.getValue(), context); QueryParamSet.addQueryParam(qp, context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else PrimitiveCypherWriter.writePrimitiveValue(property.getValue(), context, context.buffer); } } private static void toCypherExpression(PatternElement element, WriterContext context) { if (element instanceof PatternNode) { PatternNode n = (PatternNode)element; context.buffer.append('('); if (n.getJcElement() != null) ValueWriter.toValueExpression(n.getJcElement(), context, context.buffer); for (String label : n.getLabels()) { context.buffer.append(':'); context.buffer.append(label); } PatternCypherWriter.appendProperties(n, context); context.buffer.append(')'); } else if (element instanceof PatternRelation) { PatternRelation r = (PatternRelation)element; if (r.getDirection() == Direction.IN) context.buffer.append('<'); context.buffer.append('-'); boolean hasContent = r.getJcElement() != null || r.getTypes().size() > 0 || r.getMinHops() != 1 || r.getMaxHops() != 1 || r.hasProperties(); if (hasContent) context.buffer.append('['); if (r.getJcElement() != null) ValueWriter.toValueExpression(r.getJcElement(), context, context.buffer); int idx = 0; for (String type : r.getTypes()) { if (idx > 0) context.buffer.append('|'); context.buffer.append(':'); context.buffer.append(type); idx++; } if (r.getMinHops() == 0 && r.getMaxHops() == -1) // hops unbound context.buffer.append('*'); else if (r.getMinHops() == 0) { context.buffer.append("*.."); context.buffer.append(r.getMaxHops()); } else if (r.getMaxHops() == -1) { context.buffer.append('*'); context.buffer.append(r.getMinHops()); context.buffer.append(".."); } else if (r.getMinHops() != 1 || r.getMaxHops() != 1) { context.buffer.append('*'); context.buffer.append(r.getMinHops()); context.buffer.append(".."); context.buffer.append(r.getMaxHops()); } PatternCypherWriter.appendProperties(r, context); if (hasContent) context.buffer.append(']'); context.buffer.append('-'); if (r.getDirection() == Direction.OUT) context.buffer.append('>'); } } private static void appendProperties(PatternElement element, WriterContext context) { if (element.getProperties().size() > 0) { context.buffer.append('{'); StringBuilder buf = context.buffer; context.buffer = new StringBuilder(); int idx = 0; QueryParamSet.createAddParamSet(context); int paramIdx = QueryParam.getParamIndex(context); for (PatternProperty property : element.getProperties()) { if (idx > 0) context.buffer.append(", "); PatternCypherWriter.toCypherExpression(property, context); idx++; } if (inDontUseParamSet(context.currentClause)) QueryParamSet.disableUseSet(context); if (QueryParam.isExtractParams(context) && QueryParamSet.canUseSet(context) && QueryParamSet.getCurrentSet(context).getQueryParams().size() > 1) { QueryParam.setParamIndex(paramIdx, context); context.buffer = buf; PrimitiveCypherWriter.writeParameterSet(QueryParamSet.getCurrentSet(context), context); } else { buf.append(context.buffer); context.buffer = buf; } QueryParamSet.finishParamSet(context); context.buffer.append('}'); } } } /*****************************************************/ private static class StartCypherWriter { private static void toCypherExpression(StartExpression sx, WriterContext context) { JcElement jcElem = sx.getJcElement(); ValueWriter.toValueExpression(jcElem, context, context.buffer); context.buffer.append(" = "); if (jcElem instanceof JcNode) context.buffer.append("node"); else context.buffer.append("relationship"); if (sx.isAll()) { context.buffer.append("(*)"); } else if (sx.getIndexOrId().getIndexName() != null) { context.buffer.append(':'); context.buffer.append(sx.getIndexOrId().getIndexName()); context.buffer.append('('); PropertyOrQuery poq = sx.getPropertyOrQuery(); if (poq.getLuceneQuery() != null) { if (QueryParam.isExtractParams(context)) { QueryParam qp = QueryParam.createAddParam(null, poq.getLuceneQuery(), context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else { context.buffer.append('"'); context.buffer.append(poq.getLuceneQuery()); context.buffer.append('"'); } } else if (poq.getPropertyValue() != null) { context.buffer.append(poq.getPropertyName()); context.buffer.append(" = "); if (QueryParam.isExtractParams(context)) { QueryParam qp = QueryParam.createAddParam(null, poq.getPropertyValue(), context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else PrimitiveCypherWriter.writePrimitiveValue(poq.getPropertyValue(), context, context.buffer); } context.buffer.append(')'); } else if (sx.getIndexOrId().getIds() != null) { context.buffer.append('('); if (QueryParam.isExtractParams(context)) { Object val; if (sx.getIndexOrId().getIds().size() == 1) val = sx.getIndexOrId().getIds().get(0); else val = sx.getIndexOrId().getIds(); QueryParam qp = QueryParam.createAddParam(null, val, context); PrimitiveCypherWriter.writeParameter(qp, context.buffer); } else { boolean first = true; for (Long id : sx.getIndexOrId().getIds()) { if (!first) context.buffer.append(", "); context.buffer.append(id.toString()); first = false; } } context.buffer.append(')'); } } } /*****************************************************/ private static class UCypherWriter { private static void toCypherExpression(UsingExpression ux, WriterContext context) { JcValue vr = ux.getValueRef(); if (vr instanceof JcNode) // index scan context.buffer.append(ValueAccess.getName(vr)); else if (vr instanceof JcProperty) // label scan context.buffer.append(ValueAccess.getName((JcValue)ValueAccess.getPredecessor(vr))); context.buffer.append(':'); context.buffer.append(ux.getIndexLabel()); if (vr instanceof JcProperty) { context.buffer.append('('); context.buffer.append(ValueAccess.getName(vr)); context.buffer.append(')'); } } } /*****************************************************/ private static class CaseCypherWriter { private static void toCaseExpression(CaseExpression cx, WriterContext context) { JcValue cv = cx.getCaseValue(); if (cv != null) ValueWriter.toValueExpression(cv, context, context.buffer); } private static void toWhenExpression(PredicateExpression px, WriterContext context) { PredicateCypherWriter.toCypherExpression(px, context); } private static void toEndExpression(CaseExpression cx, WriterContext context) { if (cx.getEndAlias() != null) { context.buffer.append(" AS "); ValueWriter.toValueExpression(cx.getEndAlias(), context, context.buffer); } } } /*****************************************************/ public static class PrimitiveCypherWriter { public static void writePrimitiveValue(Object val, WriterContext context, StringBuilder sb) { if (val instanceof Number) { sb.append(val.toString()); } else if (val instanceof Boolean) { sb.append(val.toString()); } else if (val instanceof Collection<?>) { sb.append('['); Collection<?> coll = (Collection<?>)val; Iterator<?> it = coll.iterator(); int idx = 0; while(it.hasNext()) { if (idx > 0) sb.append(", "); PrimitiveCypherWriter.writePrimitiveValue(it.next(), context, sb); idx++; } sb.append(']'); } else if (val instanceof JcValue) { sb.append(ValueAccess.getName((JcValue)val)); } else if (val instanceof JcQueryParameter) { Object pval = ((JcQueryParameter)val).getValue(); pval = pval == null ? "NOT_SET" : pval; PrimitiveCypherWriter.writePrimitiveValue(pval, context, sb); } else { String str = val.toString(); String escaped = str.replace("\\", "\\\\").replace("'", "\\'"); sb.append('\''); sb.append(escaped); sb.append('\''); } } private static void writeParameter(QueryParam param, StringBuilder sb) { sb.append('{'); sb.append(param.getKey()); sb.append('}'); } private static void writeParameterSet(QueryParamSet paramSet, WriterContext context) { context.buffer.append(paramSet.getKey()); } } }