diff --git a/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/CompiledMathHelper.java b/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/CompiledMathHelper.java new file mode 100644 index 0000000000000..66a63b14ba935 --- /dev/null +++ b/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/CompiledMathHelper.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2002-2015 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v2_3.birk; + +import org.neo4j.cypher.internal.compiler.v2_3.CypherTypeException; + +import java.lang.reflect.Array; +import java.util.Arrays; + +/** + * This is a helper class used by compiled plans for doing basic math operations + */ +public final class CompiledMathHelper +{ + /** + * Do not instantiate this class + */ + private CompiledMathHelper() + { + } + + /** + * Utility function for doing addition + */ + public static Object add( Object op1, Object op2 ) + { + if ( op1 == null || op2 == null ) + { + return null; + } + + if ( op1 instanceof String || op2 instanceof String ) + { + return String.valueOf( op1 ) + String.valueOf( op2 ); + } + + //array multiplication + Class op1Class = op1.getClass(); + Class op2Class = op2.getClass(); + if ( op1Class.isArray() && op2Class.isArray()) + { + return addArrays( op1, op2 ); + } + else if ( op1Class.isArray() ) + { + return addArrayWithObject( op1, op2 ); + } + else if ( op2Class.isArray() ) + { + return addObjectWithArray( op1, op2 ); + } + + + //From here down we assume we are dealing with numbers + if( !(op1 instanceof Number) || !(op2 instanceof Number) ){ + throw new CypherTypeException( "Cannot add " + op1.getClass().getSimpleName() + " and " + op2.getClass() + .getSimpleName(), null ); + } + + if ( op1 instanceof Long || op2 instanceof Long ) + { + return ((Number) op1).longValue() + ((Number) op2).longValue(); + } + + if ( op1 instanceof Double || op2 instanceof Double ) + { + return ((Number) op1).doubleValue() + ((Number) op2).doubleValue(); + } + + if ( op1 instanceof Float || op2 instanceof Float ) + { + return ((Number) op1).floatValue() + ((Number) op2).floatValue(); + } + + if ( op1 instanceof Integer || op2 instanceof Integer ) + { + return ((Number) op1).intValue() + ((Number) op2).intValue(); + } + + + throw new CypherTypeException( "Cannot add " + op1.getClass().getSimpleName() + " and " + op2.getClass() + .getSimpleName(), null ); + } + + /** + * Both a1 and a2 must be arrays + */ + private static Object addArrays( Object a1, Object a2 ) + { + int l1 = Array.getLength( a1 ); + int l2 = Array.getLength( a2 ); + Object[] ret = new Object[l1 + l2]; + for ( int i = 0; i < l1; i++ ) + { + ret[i] = Array.get( a1, i ); + } + for ( int i = 0; i < l2; i++ ) + { + ret[l1 + i] = Array.get( a2, i ); + } + return ret; + } + + /** + * array must be an array + */ + private static Object addArrayWithObject( Object array, Object object ) + { + int l = Array.getLength( array ); + Object[] ret = new Object[l + 1]; + int i = 0; + for (; i < l; i++ ) + { + ret[i] = Array.get( array, i ); + } + ret[i] = object; + + return ret; + } + + /** + * array must be an array + */ + private static Object addObjectWithArray( Object object, Object array ) + { + int l = Array.getLength( array ); + Object[] ret = new Object[l + 1]; + ret[0] = object; + for (int i = 1; i < ret.length; i++ ) + { + ret[i] = Array.get( array, i ); + } + + return ret; + } +} diff --git a/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/Javac.java b/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/Javac.java index 721a57fc747ce..40a7ffcc9e04e 100644 --- a/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/Javac.java +++ b/community/cypher/cypher-compiler-2.3/src/main/java/org/neo4j/cypher/internal/compiler/v2_3/birk/Javac.java @@ -92,11 +92,11 @@ public static Class compile( String className, String c } public static InternalExecutionResult newInstance( Class clazz, Statement statement, - GraphDatabaseService db, ExecutionMode executionMode, InternalPlanDescription description, HashMap params) + GraphDatabaseService db, ExecutionMode executionMode, InternalPlanDescription description, Map params) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor constructor = - clazz.getDeclaredConstructor( Statement.class, GraphDatabaseService.class, ExecutionMode.class, InternalPlanDescription.class , HashMap.class); + clazz.getDeclaredConstructor( Statement.class, GraphDatabaseService.class, ExecutionMode.class, InternalPlanDescription.class , Map.class); return constructor.newInstance( statement, db, executionMode, description, params ); } diff --git a/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGenerator.scala b/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGenerator.scala index a08486c06e182..40a6ab2adae65 100644 --- a/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGenerator.scala +++ b/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGenerator.scala @@ -25,8 +25,8 @@ import java.util.concurrent.atomic.AtomicInteger import org.neo4j.cypher.internal.ExecutionMode import org.neo4j.cypher.internal.compiler.v2_3.helpers.Eagerly import org.neo4j.cypher.internal.compiler.v2_3.{PropertyKeyId, CostPlannerName} -import org.neo4j.cypher.internal.compiler.v2_3.ast.{Literal, Parameter, Identifier, Property} -import org.neo4j.cypher.internal.compiler.v2_3.birk.CodeGenerator.JavaTypes.{INT, LONG, OBJECT} +import org.neo4j.cypher.internal.compiler.v2_3.ast._ +import org.neo4j.cypher.internal.compiler.v2_3.birk.CodeGenerator.JavaTypes.{INT, LONG, OBJECT, DOUBLE, STRING} import org.neo4j.cypher.internal.compiler.v2_3.birk.il._ import org.neo4j.cypher.internal.compiler.v2_3.executionplan.{PlanFingerprint, CompiledPlan} import org.neo4j.cypher.internal.compiler.v2_3.planDescription.InternalPlanDescription @@ -80,6 +80,10 @@ object CodeGenerator { val LONG = "long" val INT = "int" val OBJECT = "Object" + val OBJECTARRAY = "Object[]" + val DOUBLE = "double" + val STRING = "String" + val NUMBER = "Number" } } @@ -174,7 +178,7 @@ class CodeGenerator(semanticTable: SemanticTable) { |import org.neo4j.graphdb.Result; |import org.neo4j.cypher.internal.compiler.v2_3.planDescription.InternalPlanDescription; |import org.neo4j.cypher.internal.ExecutionMode; - |import java.util.HashMap; + |import java.util.Map; | |$imports | @@ -184,9 +188,9 @@ class CodeGenerator(semanticTable: SemanticTable) { |private final GraphDatabaseService db; |private final InternalPlanDescription description; |private final ExecutionMode executionMode; - |private final HashMap params; + |private final Map params; | - |public $className( Statement statement, GraphDatabaseService db, ExecutionMode executionMode, InternalPlanDescription description, HashMap params ) + |public $className( Statement statement, GraphDatabaseService db, ExecutionMode executionMode, InternalPlanDescription description, Map params ) |{ | this.ro = statement.readOperations( ); | this.db = db; @@ -300,43 +304,54 @@ class CodeGenerator(semanticTable: SemanticTable) { val (x, action) = consume(stack.top, plan, stack.pop) (x, WhileLoop(relVar, ExpandC(variables(fromNode).name, relVar.name, dir, relTypes.map(t => variableName.next -> t.name).toMap,nodeVar.name, action), Instruction.empty)) - case Projection(left, expressions) => - val projectionInstructions = - expressions.map { - case (identifier, nodeOrRel@Identifier(name)) if semanticTable.isNode(nodeOrRel) || semanticTable.isRelationship(nodeOrRel) => - //just project the new names - variables += identifier -> variables(name) - None - - case (identifier, Property(node@Identifier(name), propKey)) if semanticTable.isNode(node) => - val token = propKey.id(semanticTable).map(_.id) - val projection = ProjectNodeProperty(token, propKey.name, variables(name).name, variableName) - variables += identifier -> projection.projectedVariable - Some(projection) - - case (identifier, Property(rel@Identifier(name), propKey)) if semanticTable.isRelationship(rel) => - val token = propKey.id(semanticTable).map(_.id) - val projection = ProjectRelProperty(token, propKey.name, variables(name).name, variableName) - variables += identifier -> projection.projectedVariable - Some(projection) - - case (identifier, Parameter(name)) => - val projection = ProjectParameter(name) - variables += identifier -> projection.projectedVariable - Some(projection) - //TODO be smarter about boxing - case (identifier, lit: Literal) => - val projection = ProjectLiteral(lit.value) - variables += identifier -> projection.projectedVariable - Some(projection) - - case other => throw new CantCompileQueryException(s"Projections of $other not yet supported") - - }.flatten.toSeq + case Projection(_, expressions) => + def findProjectionInstruction(expression: Expression): ProjectionInstruction = expression match { + case nodeOrRel@Identifier(name) if semanticTable.isNode(nodeOrRel) || semanticTable.isRelationship(nodeOrRel) => + ProjectNodeOrRelationship(variables(name)) + + case Property(node@Identifier(name), propKey) if semanticTable.isNode(node) => + val token = propKey.id(semanticTable).map(_.id) + ProjectNodeProperty(token, propKey.name, variables(name).name, variableName) + + case Property(rel@Identifier(name), propKey) if semanticTable.isRelationship(rel) => + val token = propKey.id(semanticTable).map(_.id) + ProjectRelProperty(token, propKey.name, variables(name).name, variableName) + + case Parameter(name) => ProjectParameter(name) + + case lit: IntegerLiteral => + ProjectLiteral(JavaSymbol(s"${lit.value.toString}L", LONG)) + + case lit: DoubleLiteral => + ProjectLiteral(JavaSymbol(lit.value.toString, DOUBLE)) + + case lit: StringLiteral => + ProjectLiteral(JavaSymbol(s""""${lit.value}"""", STRING)) + + case lit: Literal => + ProjectLiteral(JavaSymbol(lit.value.toString, OBJECT)) + + case Collection(exprs) => + ProjectCollection(exprs.map(findProjectionInstruction)) + + case Add(lhs, rhs) => + val leftOp = findProjectionInstruction(lhs) + val rightOp = findProjectionInstruction(rhs) + ProjectAddition(leftOp, rightOp) + + case other => throw new CantCompileQueryException(s"Projections of $other not yet supported") + } + + val projectionInstructions = expressions.map { + case (identifier, expression) => + val instruction = findProjectionInstruction(expression) + variables += identifier -> instruction.projectedVariable + instruction + }.toSeq val (x, action) = consume(stack.top, plan, stack.pop) - (x, ProjectNodeProperties(projectionInstructions, action)) + (x, ProjectProperties(projectionInstructions, action)) case _ => throw new CantCompileQueryException(s"$plan is not yet supported") } diff --git a/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/il/ProjectionInstruction.scala b/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/il/ProjectionInstruction.scala index a08d3053a79f0..2d9320e27971d 100644 --- a/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/il/ProjectionInstruction.scala +++ b/community/cypher/cypher-compiler-2.3/src/main/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/il/ProjectionInstruction.scala @@ -19,9 +19,9 @@ */ package org.neo4j.cypher.internal.compiler.v2_3.birk.il -import org.neo4j.cypher.internal.compiler.v2_3.birk.CodeGenerator.JavaTypes.OBJECT -import org.neo4j.cypher.internal.compiler.v2_3.birk.{Namer, CodeGenerator, JavaSymbol} - +import org.neo4j.cypher.internal.compiler.v2_3.CypherTypeException +import org.neo4j.cypher.internal.compiler.v2_3.birk.CodeGenerator.JavaTypes.{DOUBLE, LONG, OBJECT, OBJECTARRAY, STRING} +import org.neo4j.cypher.internal.compiler.v2_3.birk.{CodeGenerator, JavaSymbol, Namer} sealed trait ProjectionInstruction extends Instruction { def projectedVariable: JavaSymbol @@ -34,7 +34,7 @@ case class ProjectNodeProperty(token: Option[Int], propName: String, nodeIdVar: def generateInit() = if (token.isEmpty) s"""if ( $propKeyVar == -1 ) |{ - |$propKeyVar = ro.propertyKeyGetForName( "${propName}" ); + |$propKeyVar = ro.propertyKeyGetForName( "$propName" ); |} """.stripMargin else "" @@ -53,10 +53,9 @@ case class ProjectRelProperty(token: Option[Int], propName: String, relIdVar: St def generateInit() = if (token.isEmpty) s"""if ( $propKeyVar == -1 ) - |{ - |$propKeyVar = ro.propertyKeyGetForName( "$propName" ); - |} - """.stripMargin + |{ + |$propKeyVar = ro.propertyKeyGetForName( "$propName" ); + |}""".stripMargin else "" override def _importedClasses() = @@ -77,16 +76,61 @@ case class ProjectParameter(key: String) extends ProjectionInstruction { def fields() = "" } -case class ProjectLiteral(literal: AnyRef) extends ProjectionInstruction { +case class ProjectLiteral(projectedVariable: JavaSymbol) extends ProjectionInstruction { + + def generateInit() = "" + + def fields() = "" +} + +case class ProjectNodeOrRelationship(projectedVariable: JavaSymbol) extends ProjectionInstruction { + + def generateInit() = "" + + def fields() = "" +} + +case class ProjectAddition(lhs: ProjectionInstruction, rhs: ProjectionInstruction) extends ProjectionInstruction { + + def projectedVariable: JavaSymbol = { + val leftTerm = lhs.projectedVariable + val rightTerm = rhs.projectedVariable + (leftTerm.javaType, rightTerm.javaType) match { + case (LONG, LONG) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", LONG) + case (LONG, DOUBLE) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", DOUBLE) + case (LONG, STRING) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", STRING) + + case (DOUBLE, DOUBLE) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", DOUBLE) + case (DOUBLE, LONG) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", DOUBLE) + case (DOUBLE, STRING) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", STRING) + + case (STRING, STRING) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", STRING) + case (STRING, LONG) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", STRING) + case (STRING, DOUBLE) => JavaSymbol(s"${leftTerm.name} + ${rightTerm.name}", STRING) + + case (_, _) => JavaSymbol(s"CompiledMathHelper.add( ${leftTerm.name}, ${rightTerm.name} )", OBJECT) + } + } + + def generateInit() = "" + + def fields() = "" + + override def _importedClasses(): Set[String] = Set("org.neo4j.cypher.internal.compiler.v2_3.birk.CompiledMathHelper") +} + +case class ProjectCollection(instructions: Seq[ProjectionInstruction]) extends ProjectionInstruction { + + override def children: Seq[Instruction] = instructions def generateInit() = "" - def projectedVariable = JavaSymbol(s"$literal", OBJECT) + def projectedVariable = JavaSymbol(instructions.map(_.projectedVariable.name).mkString("new Object[]{", ",", "}"), OBJECTARRAY) def fields() = "" } -case class ProjectNodeProperties(projections:Seq[ProjectionInstruction], parent:Instruction) extends Instruction { +case class ProjectProperties(projections:Seq[ProjectionInstruction], parent:Instruction) extends Instruction { override def generateCode()= generate(_.generateCode()) override protected def children: Seq[Instruction] = projections :+ parent diff --git a/community/cypher/cypher-compiler-2.3/src/test/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGeneratorTest.scala b/community/cypher/cypher-compiler-2.3/src/test/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGeneratorTest.scala index 84e44ef13a177..4426dd5892927 100644 --- a/community/cypher/cypher-compiler-2.3/src/test/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGeneratorTest.scala +++ b/community/cypher/cypher-compiler-2.3/src/test/scala/org/neo4j/cypher/internal/compiler/v2_3/birk/CodeGeneratorTest.scala @@ -26,7 +26,7 @@ import org.mockito.stubbing.Answer import org.neo4j.collection.primitive.PrimitiveLongIterator import org.neo4j.cypher.internal.NormalMode import org.neo4j.cypher.internal.commons.CypherFunSuite -import org.neo4j.cypher.internal.compiler.v2_3.ast.{Parameter, SignedDecimalIntegerLiteral, RelTypeName} +import org.neo4j.cypher.internal.compiler.v2_3.ast._ import org.neo4j.cypher.internal.compiler.v2_3.executionplan.InternalExecutionResult import org.neo4j.cypher.internal.compiler.v2_3.pipes.LazyLabel import org.neo4j.cypher.internal.compiler.v2_3.planner.{SemanticTable, LogicalPlanningTestSupport} @@ -38,7 +38,6 @@ import org.neo4j.kernel.api.ReadOperations import org.neo4j.kernel.impl.api.RelationshipVisitor import org.neo4j.kernel.impl.api.store.RelationshipIterator -//TODO This test should be removed at some point, maintaining a mocked database is not worth it class CodeGeneratorTest extends CypherFunSuite with LogicalPlanningTestSupport { private val generator = new CodeGenerator(mock[SemanticTable]) @@ -288,6 +287,79 @@ class CodeGeneratorTest extends CypherFunSuite with LogicalPlanningTestSupport { result.toSet should equal(Set(Map("a" -> "BAR"))) } + test("project addition of two ints") { + val lhs = SignedDecimalIntegerLiteral("1")(null) + val rhs = SignedDecimalIntegerLiteral("3")(null) + val add = Add(lhs, rhs)(null) + + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> add))(solved)) + val compiled = compile(plan) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> 4))) + } + + test("project addition of int and double") { + val lhs = SignedDecimalIntegerLiteral("1")(null) + val rhs = DecimalDoubleLiteral("3.0")(null) + val add = Add(lhs, rhs)(null) + + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> add))(solved)) + val compiled = compile(plan) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> (1L + 3.0)))) + } + + test("project addition of int and String") { + val lhs = SignedDecimalIntegerLiteral("1")(null) + val rhs = StringLiteral("two")(null) + val add = Add(lhs, rhs)(null) + + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> add))(solved)) + val compiled = compile(plan) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> "1two"))) + } + + test("project addition of int and value from params") { + val lhs = SignedDecimalIntegerLiteral("1")(null) + val rhs = Parameter("FOO")(null) + val add = Add(lhs, rhs)(null) + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> add))(solved)) + val compiled = compile(plan, Map("FOO" -> Long.box(3L))) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> 4))) + } + + test("project addition of two values coming from params") { + val lhs = Parameter("FOO")(null) + val rhs = Parameter("BAR")(null) + val add = Add(lhs, rhs)(null) + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> add))(solved)) + val compiled = compile(plan, Map("FOO" -> Long.box(3L), "BAR" -> Long.box(1L))) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> 4))) + } + + test("project collection") { + val collection = Collection(Seq(Parameter("FOO")(null), Parameter("BAR")(null)))(null) + val plan = ProduceResult(List.empty, List.empty, List("a"), Projection(SingleRow()(solved), Map("a" -> collection))(solved)) + val compiled = compile(plan, Map("FOO" -> Long.box(3L), "BAR" -> Long.box(1L))) + + //then + val result = getResult(compiled, "a") + result.toSet should equal(Set(Map("a" -> List(3, 1)))) + } + private def compile(plan: LogicalPlan, params: Map[String, AnyRef] = Map.empty) = { val compiled = generator.generate(plan, newMockedPlanContext, Clock.SYSTEM_CLOCK) compiled.executionResultBuilder(statement, graphDatabaseService, NormalMode, params) @@ -439,7 +511,7 @@ class CodeGeneratorTest extends CypherFunSuite with LogicalPlanningTestSupport { } private def getResult(plan: InternalExecutionResult, columns: String*) = { - val res= Seq.newBuilder[Map[String, AnyRef]] + val res= Seq.newBuilder[Map[String, Any]] plan.accept(new ResultVisitor[RuntimeException]() { override def visit(element: ResultRow): Boolean = { @@ -447,8 +519,22 @@ class CodeGeneratorTest extends CypherFunSuite with LogicalPlanningTestSupport { true } }) - res.result() + res.result().toSeq.withArraysAsLists } + /** + * Get rid of Arrays to make it easier to compare results by equality. + */ + implicit class RichInternalExecutionResults(res: InternalExecutionResult) { + def toComparableList: Seq[Map[String, Any]] = res.toList.withArraysAsLists + } + implicit class RichMapSeq(res: Seq[Map[String, Any]]) { + def withArraysAsLists: Seq[Map[String, Any]] = res.map((map: Map[String, Any]) => + map.map { + case (k, a: Array[_]) => k -> a.toList + case m => m + } + ) + } } diff --git a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/MatchAcceptanceTest.scala b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/MatchAcceptanceTest.scala index cb060a058517c..1e64891ad36b4 100644 --- a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/MatchAcceptanceTest.scala +++ b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/MatchAcceptanceTest.scala @@ -19,6 +19,8 @@ */ package org.neo4j.cypher +import java.util + import org.neo4j.cypher.internal.PathImpl import org.neo4j.graphdb._ @@ -2069,6 +2071,22 @@ return b result should equal(List(asResult(props, "a"))) } + test("adding a property and a literal is supported in new runtime") { + val props = Map("prop" -> 1) + createNode(props) + val result = executeWithAllPlannersAndRuntimes("MATCH a RETURN a.prop + 1 AS FOO").toComparableList + + result should equal(List(Map("FOO" -> 2))) + } + + test("adding arrays is supported in new runtime") { + val props = Map("prop1" -> Array(1,2,3), "prop2" -> Array(4, 5)) + createNode(props) + val result = executeWithAllPlannersAndRuntimes("MATCH a RETURN a.prop1 + a.prop2 AS FOO").toComparableList + + result should equal(List(Map("FOO" -> List(1, 2, 3, 4, 5)))) + } + /** * Append identifier to keys and transform value arrays to lists */ diff --git a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ReturnAcceptanceTest.scala b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ReturnAcceptanceTest.scala index 08d29b107182f..4fcf345178766 100644 --- a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ReturnAcceptanceTest.scala +++ b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ReturnAcceptanceTest.scala @@ -293,7 +293,7 @@ return coalesce(a.title, a.name)""") } test("square function returns decimals") { - val result = executeWithAllPlannersAndRuntimes("return sqrt(12.96)").toList + val result = executeWithAllPlanners("return sqrt(12.96)").toList result should equal(List(Map("sqrt(12.96)" -> 3.6))) } @@ -541,4 +541,16 @@ return coalesce(a.title, a.name)""") result.toList should equal(List(Map("1" -> 1))) } + + test("compiled runtime should support addition of collections") { + val result = executeWithAllPlannersAndRuntimes("RETURN [1,2,3] + [4, 5] AS FOO") + + result.toComparableList should equal(List(Map("FOO" -> List(1, 2, 3, 4, 5)))) + } + + test("compiled runtime should support addition of item to collection") { + val result = executeWithAllPlannersAndRuntimes("""RETURN [1,2,3] + 4 AS FOO""") + + result.toComparableList should equal(List(Map("FOO" -> List(1, 2, 3, 4)))) + } }