Skip to content

Commit

Permalink
Test generated code for ProjectionInstruction
Browse files Browse the repository at this point in the history
  • Loading branch information
thobe committed Apr 20, 2015
1 parent a855a6d commit 6f75999
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 128 deletions.
Expand Up @@ -19,10 +19,9 @@
*/ */
package org.neo4j.cypher.internal.compiler.v2_3.birk; 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.lang.reflect.Array;
import java.util.Arrays;
import org.neo4j.cypher.internal.compiler.v2_3.CypherTypeException;


/** /**
* This is a helper class used by compiled plans for doing basic math operations * This is a helper class used by compiled plans for doing basic math operations
Expand All @@ -39,64 +38,53 @@ private CompiledMathHelper()
/** /**
* Utility function for doing addition * Utility function for doing addition
*/ */
public static Object add( Object op1, Object op2 ) public static Object add( Object lhs, Object rhs )
{ {
if ( op1 == null || op2 == null ) if ( lhs == null || rhs == null )
{ {
return null; return null;
} }


if ( op1 instanceof String || op2 instanceof String ) if ( lhs instanceof String || rhs instanceof String )
{ {
return String.valueOf( op1 ) + String.valueOf( op2 ); return String.valueOf( lhs ) + String.valueOf( rhs );
} }


//array multiplication // array addition
Class<?> op1Class = op1.getClass(); Class<?> op1Class = lhs.getClass();
Class<?> op2Class = op2.getClass(); Class<?> op2Class = rhs.getClass();
if ( op1Class.isArray() && op2Class.isArray()) if ( op1Class.isArray() && op2Class.isArray())
{ {
return addArrays( op1, op2 ); return addArrays( lhs, rhs );
} }
else if ( op1Class.isArray() ) else if ( op1Class.isArray() )
{ {
return addArrayWithObject( op1, op2 ); return addArrayWithObject( lhs, rhs );
} }
else if ( op2Class.isArray() ) else if ( op2Class.isArray() )
{ {
return addObjectWithArray( op1, op2 ); return addObjectWithArray( lhs, rhs );
} }



if ( lhs instanceof Number && rhs instanceof Number )
//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 ( lhs instanceof Double || rhs instanceof Double ||
lhs instanceof Float || rhs instanceof Float )
{
return ((Number) lhs).doubleValue() + ((Number) rhs).doubleValue();
}
if ( lhs instanceof Long || rhs instanceof Long ||
lhs instanceof Integer || rhs instanceof Integer ||
lhs instanceof Short || rhs instanceof Short ||
lhs instanceof Byte || rhs instanceof Byte )
{
return ((Number) lhs).longValue() + ((Number) rhs).longValue();
}
// other numbers we cannot add
} }


if ( op1 instanceof Double || op2 instanceof Double ) throw new CypherTypeException( "Cannot add " + lhs.getClass().getSimpleName() +
{ " and " + rhs.getClass().getSimpleName(), null );
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 );
} }


/** /**
Expand Down
Expand Up @@ -23,18 +23,17 @@ import java.util
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger


import org.neo4j.cypher.internal.ExecutionMode import org.neo4j.cypher.internal.ExecutionMode
import org.neo4j.cypher.internal.compiler.v2_3.helpers.Eagerly import org.neo4j.cypher.internal.compiler.v2_3.CostPlannerName
import org.neo4j.cypher.internal.compiler.v2_3.{PropertyKeyId, CostPlannerName}
import org.neo4j.cypher.internal.compiler.v2_3.ast._ 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.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.birk.il._
import org.neo4j.cypher.internal.compiler.v2_3.executionplan.{PlanFingerprint, CompiledPlan} import org.neo4j.cypher.internal.compiler.v2_3.executionplan.{CompiledPlan, PlanFingerprint}
import org.neo4j.cypher.internal.compiler.v2_3.helpers.Eagerly
import org.neo4j.cypher.internal.compiler.v2_3.planDescription.InternalPlanDescription import org.neo4j.cypher.internal.compiler.v2_3.planDescription.InternalPlanDescription
import org.neo4j.cypher.internal.compiler.v2_3.planner.{SemanticTable, CantCompileQueryException}
import org.neo4j.cypher.internal.compiler.v2_3.planner.logical.{LogicalPlanIdentificationBuilder,
LogicalPlan2PlanDescription}
import org.neo4j.cypher.internal.compiler.v2_3.planner.logical.plans._ import org.neo4j.cypher.internal.compiler.v2_3.planner.logical.plans._
import org.neo4j.cypher.internal.compiler.v2_3.spi.{PlanContext, InstrumentedGraphStatistics} import org.neo4j.cypher.internal.compiler.v2_3.planner.logical.{LogicalPlan2PlanDescription, LogicalPlanIdentificationBuilder}
import org.neo4j.cypher.internal.compiler.v2_3.planner.{CantCompileQueryException, SemanticTable}
import org.neo4j.cypher.internal.compiler.v2_3.spi.{InstrumentedGraphStatistics, PlanContext}
import org.neo4j.graphdb.GraphDatabaseService import org.neo4j.graphdb.GraphDatabaseService
import org.neo4j.helpers.Clock import org.neo4j.helpers.Clock
import org.neo4j.kernel.api.Statement import org.neo4j.kernel.api.Statement
Expand All @@ -43,10 +42,27 @@ import scala.collection.Map
import scala.collection.immutable.Stack import scala.collection.immutable.Stack


object CodeGenerator { object CodeGenerator {
def generateClass( instructions: Seq[Instruction] ) = {
val className = nextClassName()
val source = generateCodeFromInstructions(className, instructions)
// print(indentNicely(source))
Javac.compile(s"$packageName.$className",source )
}

object JavaTypes {
val LONG = "long"
val INT = "int"
val OBJECT = "Object"
val OBJECTARRAY = "Object[]"
val DOUBLE = "double"
val STRING = "String"
val NUMBER = "Number"
}

private val packageName = "org.neo4j.cypher.internal.compiler.v2_3.birk.generated" private val packageName = "org.neo4j.cypher.internal.compiler.v2_3.birk.generated"
private val nameCounter = new AtomicInteger(0) private val nameCounter = new AtomicInteger(0)


def nextClassName(): String = { private def nextClassName(): String = {
val x = nameCounter.getAndIncrement val x = nameCounter.getAndIncrement
s"GeneratedExecutionPlan$x" s"GeneratedExecutionPlan$x"
} }
Expand Down Expand Up @@ -76,91 +92,21 @@ object CodeGenerator {


def n = System.lineSeparator() def n = System.lineSeparator()


object JavaTypes { private def generateCodeFromInstructions(className: String, instructions: Seq[Instruction]) = {
val LONG = "long"
val INT = "int"
val OBJECT = "Object"
val OBJECTARRAY = "Object[]"
val DOUBLE = "double"
val STRING = "String"
val NUMBER = "Number"
}
}

class Namer(prefix: String) {
var varCounter = 0

def next(): String = {
varCounter += 1
prefix + varCounter
}

def nextWithType(typ: String): JavaSymbol = JavaSymbol(next(), typ)
}

case class JavaSymbol(name: String, javaType: String)

class CodeGenerator {

import CodeGenerator.{n, nextClassName, packageName}
import scala.collection.JavaConverters._

def generate(plan: LogicalPlan, planContext: PlanContext, clock: Clock, semanticTable: SemanticTable) = {
plan match {
case _: ProduceResult =>
val className = nextClassName()
val planStatements: Seq[Instruction] = createResultAst(plan, semanticTable)
val source = generateCodeFromAst(className, planStatements)
val clazz = Javac.compile(s"$packageName.$className",source )

val fp = planContext.statistics match {
case igs: InstrumentedGraphStatistics =>
Some(PlanFingerprint(clock.currentTimeMillis(), planContext.txIdProvider(), igs.snapshot.freeze))
case _ =>
None
}

val idMap = LogicalPlanIdentificationBuilder(plan)
val description: InternalPlanDescription = LogicalPlan2PlanDescription(plan, idMap)

val builder = (st: Statement, db: GraphDatabaseService, mode: ExecutionMode, params: Map[String, Any]) => Javac.newInstance(clazz, st, db, mode, description, asJavaHashMap(params))

CompiledPlan(updating = false, None, fp, CostPlannerName, builder)

case _ => throw new CantCompileQueryException("Can only compile plans with ProduceResult on top")
}
}

private def asJavaHashMap(params: Map[String, Any]) = {
val jMap = new util.HashMap[String, Object]()
params.foreach {
case (key, value) => jMap.put(key, javaValue(value))
}
jMap
}

private def javaValue( value: Any ): Object = value match {
case iter: Seq[_] => iter.map( javaValue ).asJava
case iter: Map[_, _] => Eagerly.immutableMapValues( iter, javaValue ).asJava
case x: Any => x.asInstanceOf[AnyRef]

}

private def generateCodeFromAst(className: String, statements: Seq[Instruction]) = {
val importLines: Set[String] = val importLines: Set[String] =
statements. instructions.
map(_.importedClasses()). map(_.importedClasses()).
reduceOption(_ ++ _). reduceOption(_ ++ _).
getOrElse(Set.empty) getOrElse(Set.empty)


val imports = if(importLines.nonEmpty) val imports = if ( importLines.nonEmpty )
importLines.toSeq.sorted.mkString("import ", s";${n}import ", ";") importLines.toSeq.sorted.mkString( "import ", s";${n}import ", ";" )
else else
"" ""
val fields = statements.map(_.fields().trim).reduce(_ + n + _) val fields = instructions.map(_.fields().trim).reduce(_ + n + _)
val init = statements.map(_.generateInit().trim).reduce(_ + n + _) val init = instructions.map(_.generateInit().trim).reduce(_ + n + _)
val methodBody = statements.map(_.generateCode().trim).reduce(_ + n + _) val methodBody = instructions.map(_.generateCode().trim).reduce(_ + n + _)
val privateMethods = statements.flatMap(_.methods).distinct.sortBy(_.name) val privateMethods = instructions.flatMap(_.methods).distinct.sortBy(_.name)
val privateMethodText = privateMethods.map(_.generateCode.trim).reduceOption(_ + n + _).getOrElse("") val privateMethodText = privateMethods.map(_.generateCode.trim).reduceOption(_ + n + _).getOrElse("")


//TODO move imports to set and merge with the other imports, or use full paths //TODO move imports to set and merge with the other imports, or use full paths
Expand Down Expand Up @@ -230,7 +176,7 @@ class CodeGenerator {
|}""".stripMargin |}""".stripMargin
} }


private def createResultAst(plan: LogicalPlan, semanticTable: SemanticTable): Seq[Instruction] = { private def createInstructions(plan: LogicalPlan, semanticTable: SemanticTable): Seq[Instruction] = {
var variables: Map[String, JavaSymbol] = Map.empty var variables: Map[String, JavaSymbol] = Map.empty
var probeTables: Map[NodeHashJoin, CodeThunk] = Map.empty var probeTables: Map[NodeHashJoin, CodeThunk] = Map.empty
val variableName = new Namer("v") val variableName = new Namer("v")
Expand Down Expand Up @@ -362,3 +308,61 @@ class CodeGenerator {
result result
} }
} }

class Namer(prefix: String) {
var varCounter = 0

def next(): String = {
varCounter += 1
prefix + varCounter
}

def nextWithType(typ: String): JavaSymbol = JavaSymbol(next(), typ)
}

case class JavaSymbol(name: String, javaType: String)

class CodeGenerator {

import CodeGenerator.{generateClass, createInstructions}
import scala.collection.JavaConverters._

def generate(plan: LogicalPlan, planContext: PlanContext, clock: Clock, semanticTable: SemanticTable) = {
plan match {
case _: ProduceResult =>
val clazz = generateClass( createInstructions( plan, semanticTable ) )

val fp = planContext.statistics match {
case igs: InstrumentedGraphStatistics =>
Some(PlanFingerprint(clock.currentTimeMillis(), planContext.txIdProvider(), igs.snapshot.freeze))
case _ =>
None
}

val idMap = LogicalPlanIdentificationBuilder(plan)
val description: InternalPlanDescription = LogicalPlan2PlanDescription(plan, idMap)

val builder = (st: Statement, db: GraphDatabaseService, mode: ExecutionMode, params: Map[String, Any]) => Javac.newInstance(clazz, st, db, mode, description, asJavaHashMap(params))

CompiledPlan(updating = false, None, fp, CostPlannerName, builder)

case _ => throw new CantCompileQueryException("Can only compile plans with ProduceResult on top")
}
}

private def asJavaHashMap(params: Map[String, Any]) = {
val jMap = new util.HashMap[String, Object]()
params.foreach {
case (key, value) => jMap.put(key, javaValue(value))
}
jMap
}

private def javaValue( value: Any ): Object = value match {
case iter: Seq[_] => iter.map( javaValue ).asJava
case iter: Map[_, _] => Eagerly.immutableMapValues( iter, javaValue ).asJava
case x: Any => x.asInstanceOf[AnyRef]
}


}
Expand Up @@ -19,7 +19,6 @@
*/ */
package org.neo4j.cypher.internal.compiler.v2_3.birk.il package org.neo4j.cypher.internal.compiler.v2_3.birk.il


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.JavaTypes.{DOUBLE, LONG, OBJECT, OBJECTARRAY, STRING}
import org.neo4j.cypher.internal.compiler.v2_3.birk.{CodeGenerator, JavaSymbol, Namer} import org.neo4j.cypher.internal.compiler.v2_3.birk.{CodeGenerator, JavaSymbol, Namer}


Expand All @@ -28,6 +27,16 @@ sealed trait ProjectionInstruction extends Instruction {
def generateCode() = "" def generateCode() = ""
} }


object ProjectionInstruction {
def literal( value:Long ):ProjectionInstruction = ProjectLiteral( JavaSymbol( value.toString + "L", LONG ) )
def literal( value:Double ):ProjectionInstruction = ProjectLiteral( JavaSymbol( value.toString, DOUBLE ) )
def literal( value:String ):ProjectionInstruction = ProjectLiteral( JavaSymbol( s""""$value"""", STRING ) )
def literal( value:Boolean ):ProjectionInstruction = ???
def parameter( key:String ):ProjectionInstruction = ProjectParameter( key )

def add( lhs:ProjectionInstruction, rhs:ProjectionInstruction ):ProjectionInstruction = ProjectAddition( lhs, rhs )
}

case class ProjectNodeProperty(token: Option[Int], propName: String, nodeIdVar: String, namer: Namer) extends ProjectionInstruction { case class ProjectNodeProperty(token: Option[Int], propName: String, nodeIdVar: String, namer: Namer) extends ProjectionInstruction {
private val propKeyVar = token.map(_.toString).getOrElse(namer.next()) private val propKeyVar = token.map(_.toString).getOrElse(namer.next())


Expand Down

0 comments on commit 6f75999

Please sign in to comment.