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))))
+ }
}