Skip to content

Commit

Permalink
Fix so that we coerce when necessary in compiled expression
Browse files Browse the repository at this point in the history
In a not so distant future we should be able to rely on planning to have
properly coerced for us, but until then we need to handle it explicitly
in compiled expressions
  • Loading branch information
pontusmelke committed Jun 15, 2018
1 parent f1bb11c commit b9e73dc
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 229 deletions.
Expand Up @@ -57,7 +57,7 @@ object PredicateHelper {
//i) we do late ast rewrite after semantic analysis, so all semantic table will be missing some expression //i) we do late ast rewrite after semantic analysis, so all semantic table will be missing some expression
//ii) For WHERE a.prop semantic analysis will say that a.prop has boolean type since it belongs to a WHERE. //ii) For WHERE a.prop semantic analysis will say that a.prop has boolean type since it belongs to a WHERE.
// That makes it not usable here since we would need to coerce in that case. // That makes it not usable here since we would need to coerce in that case.
private def isPredicate(expression: Expression) = { def isPredicate(expression: Expression) = {
expression match { expression match {
case o: OperatorExpression => o.signatures.forall(_.outputType == symbols.CTBoolean) case o: OperatorExpression => o.signatures.forall(_.outputType == symbols.CTBoolean)
case f: FunctionInvocation => BOOLEAN_FUNCTIONS.contains(f.function) case f: FunctionInvocation => BOOLEAN_FUNCTIONS.contains(f.function)
Expand Down
Expand Up @@ -58,150 +58,133 @@ private CypherBoolean()


public static Value xor( AnyValue lhs, AnyValue rhs ) public static Value xor( AnyValue lhs, AnyValue rhs )
{ {
boolean seenNull = false;
if ( lhs == NO_VALUE || rhs == NO_VALUE )
{
return NO_VALUE;
}

return (lhs == Values.TRUE) ^ (rhs == Values.TRUE) ? Values.TRUE : Values.FALSE; return (lhs == Values.TRUE) ^ (rhs == Values.TRUE) ? Values.TRUE : Values.FALSE;
} }


public static Value not( AnyValue in ) public static Value not( AnyValue in )
{ {
if ( in == NO_VALUE )
{
return NO_VALUE;
}

return in != Values.TRUE ? Values.TRUE : Values.FALSE; return in != Values.TRUE ? Values.TRUE : Values.FALSE;
} }


public static Value equals( AnyValue lhs, AnyValue rhs ) public static Value equals( AnyValue lhs, AnyValue rhs )
{ {
Boolean equals = lhs.ternaryEquals( rhs ); Boolean compare = lhs.ternaryEquals( rhs );
if ( equals == null ) if ( compare == null )
{ {
return NO_VALUE; return NO_VALUE;
} }
else return compare ? Values.TRUE : Values.FALSE;
{
return equals ? Values.TRUE : Values.FALSE;
}
} }


public static Value notEquals( AnyValue lhs, AnyValue rhs ) public static Value notEquals( AnyValue lhs, AnyValue rhs )
{ {
Boolean equals = lhs.ternaryEquals( rhs ); Boolean compare = lhs.ternaryEquals( rhs );
if ( equals == null ) if ( compare == null )
{ {
return NO_VALUE; return NO_VALUE;
} }
else return compare ? Values.FALSE : Values.TRUE;
{
return equals ? Values.FALSE : Values.TRUE;
}
} }


public static BooleanValue coerceToBoolean( AnyValue value ) public static Value coerceToBoolean( AnyValue value )
{ {
return value.map( BOOLEAN_MAPPER ) ? Values.TRUE : Values.FALSE; return value.map( BOOLEAN_MAPPER );
} }


private static final class BooleanMapper implements ValueMapper<Boolean> private static final class BooleanMapper implements ValueMapper<Value>
{ {
@Override @Override
public Boolean mapPath( PathValue value ) public Value mapPath( PathValue value )
{ {
return value.size() > 0; return value.size() > 0 ? Values.TRUE : Values.FALSE;
} }


@Override @Override
public Boolean mapNode( VirtualNodeValue value ) public Value mapNode( VirtualNodeValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapRelationship( VirtualRelationshipValue value ) public Value mapRelationship( VirtualRelationshipValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapMap( MapValue value ) public Value mapMap( MapValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapNoValue() public Value mapNoValue()
{ {
return false; return NO_VALUE;
} }


@Override @Override
public Boolean mapSequence( SequenceValue value ) public Value mapSequence( SequenceValue value )
{ {
return value.length() > 0; return value.length() > 0 ? Values.TRUE : Values.FALSE;
} }


@Override @Override
public Boolean mapText( TextValue value ) public Value mapText( TextValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapBoolean( BooleanValue value ) public Value mapBoolean( BooleanValue value )
{ {
return value.booleanValue(); return value;
} }


@Override @Override
public Boolean mapNumber( NumberValue value ) public Value mapNumber( NumberValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapDateTime( DateTimeValue value ) public Value mapDateTime( DateTimeValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapLocalDateTime( LocalDateTimeValue value ) public Value mapLocalDateTime( LocalDateTimeValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapDate( DateValue value ) public Value mapDate( DateValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapTime( TimeValue value ) public Value mapTime( TimeValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapLocalTime( LocalTimeValue value ) public Value mapLocalTime( LocalTimeValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapDuration( DurationValue value ) public Value mapDuration( DurationValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }


@Override @Override
public Boolean mapPoint( PointValue value ) public Value mapPoint( PointValue value )
{ {
throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null ); throw new CypherTypeException( "Don't know how to treat that as a boolean: " + value, null );
} }
Expand Down
Expand Up @@ -22,7 +22,8 @@
*/ */
package org.neo4j.cypher.internal.runtime.compiled.expressions; package org.neo4j.cypher.internal.runtime.compiled.expressions;


import org.neo4j.cypher.CypherTypeException; import org.opencypher.v9_0.util.CypherTypeException;

import org.neo4j.values.AnyValue; import org.neo4j.values.AnyValue;
import org.neo4j.values.storable.BooleanValue; import org.neo4j.values.storable.BooleanValue;
import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Value;
Expand Down
Expand Up @@ -24,6 +24,7 @@ package org.neo4j.cypher.internal.runtime.compiled.expressions


import org.neo4j.cypher.internal.compatibility.v3_5.runtime.SlotConfiguration import org.neo4j.cypher.internal.compatibility.v3_5.runtime.SlotConfiguration
import org.neo4j.cypher.internal.compatibility.v3_5.runtime.ast._ import org.neo4j.cypher.internal.compatibility.v3_5.runtime.ast._
import org.neo4j.cypher.internal.compiler.v3_5.helpers.PredicateHelper.isPredicate
import org.neo4j.cypher.internal.runtime.DbAccess import org.neo4j.cypher.internal.runtime.DbAccess
import org.neo4j.cypher.internal.runtime.compiled.expressions.IntermediateRepresentation.method import org.neo4j.cypher.internal.runtime.compiled.expressions.IntermediateRepresentation.method
import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext
Expand Down Expand Up @@ -178,66 +179,88 @@ class IntermediateCodeGeneration(slots: SlotConfiguration) {
case Or(lhs, rhs) => case Or(lhs, rhs) =>
for {l <- compile(lhs) for {l <- compile(lhs)
r <- compile(rhs) r <- compile(rhs)
} yield generateOrs(List(l, r)) } yield {
val left = if (isPredicate(lhs)) l else coerceToPredicate(l)
val right = if (isPredicate(rhs)) r else coerceToPredicate(r)
generateOrs(List(left, right))
}


case Ors(expressions) => case Ors(expressions) =>
val compiled = expressions.foldLeft[Option[List[IntermediateExpression]]](Some(List.empty)) { (acc, current) => val compiled = expressions.foldLeft[Option[List[(IntermediateExpression, Boolean)]]](Some(List.empty)) { (acc, current) =>
for {l <- acc for {l <- acc
e <- compile(current)} yield l :+ e e <- compile(current)} yield l :+ (e -> isPredicate(current))
} }


for (e <- compiled) yield e match { for (e <- compiled) yield e match {
case Nil => IntermediateExpression(truthValue, nullable = false) //this will not really happen because of rewriters etc case Nil => IntermediateExpression(truthValue, nullable = false) //this will not really happen because of rewriters etc
case a :: Nil => a case (a, isPredicate) :: Nil => if (isPredicate) a else coerceToPredicate(a)
case list => generateOrs(list) case list =>
val coerced = list.map {
case (p, true) => p
case (p, false) => coerceToPredicate(p)
}
generateOrs(coerced)
} }


case Xor(lhs, rhs) => case Xor(lhs, rhs) =>
for {l <- compile(lhs) for {l <- compile(lhs)
r <- compile(rhs) r <- compile(rhs)
} yield IntermediateExpression(invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("xor"), l.ir, r.ir), } yield {
l.nullable | l.nullable) val left = if (isPredicate(lhs)) l else coerceToPredicate(l)
val right = if (isPredicate(rhs)) r else coerceToPredicate(r)
IntermediateExpression(
noValueCheck(left, right)(invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("xor"), left.ir, right.ir)),
left.nullable | right.nullable)
}


case And(lhs, rhs) => case And(lhs, rhs) =>
for {l <- compile(lhs) for {l <- compile(lhs)
r <- compile(rhs) r <- compile(rhs)
} yield generateAnds(List(l, r)) } yield {
val left = if (isPredicate(lhs)) l else coerceToPredicate(l)
val right = if (isPredicate(rhs)) r else coerceToPredicate(r)
generateAnds(List(left, right))
}


case Ands(expressions) => case Ands(expressions) =>
val compiled = expressions.foldLeft[Option[List[IntermediateExpression]]](Some(List.empty)) { (acc, current) => val compiled = expressions.foldLeft[Option[List[(IntermediateExpression, Boolean)]]](Some(List.empty)) { (acc, current) =>
for {l <- acc for {l <- acc
e <- compile(current)} yield l :+ e e <- compile(current)} yield l :+ (e -> isPredicate(current))
} }


for (e <- compiled) yield e match { for (e <- compiled) yield e match {
case Nil => IntermediateExpression(truthValue, nullable = false) //this will not really happen because of rewriters etc case Nil => IntermediateExpression(truthValue, nullable = false) //this will not really happen because of rewriters etc
case a :: Nil => a case (a, isPredicate) :: Nil => if (isPredicate) a else coerceToPredicate(a)
case list => generateAnds(list) case list =>
val coerced = list.map {
case (p, true) => p
case (p, false) => coerceToPredicate(p)
}
generateAnds(coerced)
} }


case Not(arg) => case Not(arg) =>
compile(arg).map(a => compile(arg).map(a => {
val in = if (isPredicate(arg)) a else coerceToPredicate(a)
IntermediateExpression( IntermediateExpression(
invokeStatic(method[CypherBoolean, Value, AnyValue]("not"), a.ir), a.nullable)) noValueCheck(in)(invokeStatic(method[CypherBoolean, Value, AnyValue]("not"), in.ir)), in.nullable)
})


case Equals(lhs, rhs) => case Equals(lhs, rhs) =>
for {l <- compile(lhs) for {l <- compile(lhs)
r <- compile(rhs) r <- compile(rhs)
} yield IntermediateExpression( } yield IntermediateExpression(invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("equals"), l.ir, r.ir),
invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("equals"), l.ir, r.ir), l.nullable | r.nullable) l.nullable | r.nullable)




case NotEquals(lhs, rhs) => case NotEquals(lhs, rhs) =>
for {l <- compile(lhs) for {l <- compile(lhs)
r <- compile(rhs) r <- compile(rhs)
} yield IntermediateExpression( } yield IntermediateExpression(invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("notEquals"), l.ir, r.ir),
invokeStatic(method[CypherBoolean, Value, AnyValue, AnyValue]("notEquals"), l.ir, r.ir), l.nullable | r.nullable) l.nullable | r.nullable)

case CoerceToPredicate(inner) => compile(inner).map(coerceToPredicate)


case CoerceToPredicate(inner) =>
compile(inner).map(e =>
IntermediateExpression(
invokeStatic(method[CypherBoolean, BooleanValue, AnyValue]("coerceToBoolean"), e.ir),
nullable = false))
//data access //data access
case Parameter(name, _) => //TODO parameters that are autogenerated from literals should have nullable = false case Parameter(name, _) => //TODO parameters that are autogenerated from literals should have nullable = false
Some(IntermediateExpression(invoke(load("params"), method[MapValue, AnyValue, String]("get"), Some(IntermediateExpression(invoke(load("params"), method[MapValue, AnyValue, String]("get"),
Expand Down Expand Up @@ -375,6 +398,10 @@ class IntermediateCodeGeneration(slots: SlotConfiguration) {
nextName nextName
} }


private def coerceToPredicate(e: IntermediateExpression) = IntermediateExpression(
invokeStatic(method[CypherBoolean, Value, AnyValue]("coerceToBoolean"), e.ir),
nullable = e.nullable)

/** /**
* Ok AND and ANDS are complicated. At the core we try to find a single `FALSE` if we find one there is no need to look * Ok AND and ANDS are complicated. At the core we try to find a single `FALSE` if we find one there is no need to look
* at more predicates. If it doesn't find a `FALSE` it will either return `NULL` if any of the predicates has evaluated * at more predicates. If it doesn't find a `FALSE` it will either return `NULL` if any of the predicates has evaluated
Expand Down

0 comments on commit b9e73dc

Please sign in to comment.