Skip to content

Commit

Permalink
Merge pull request #10836 from thobe/3.4-temporal
Browse files Browse the repository at this point in the history
Introduce temporal types to Cypher
  • Loading branch information
sherfert committed Jan 31, 2018
2 parents 71def71 + cfe98e4 commit 7b0eb1c
Show file tree
Hide file tree
Showing 150 changed files with 10,357 additions and 526 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,48 @@ public void writePoint( CoordinateReferenceSystem crs, double[] coordinate ) thr
packNull();
}

@Override
public void writeDuration( long months, long days, long seconds, int nanos ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeDate( long epochDay ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeLocalTime( long nanoOfDay ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws IOException
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public void writeNull() throws IOException
{
Expand Down
36 changes: 35 additions & 1 deletion community/common/src/main/java/org/neo4j/time/FakeClock.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ZoneId getZone()
@Override
public Clock withZone( ZoneId zone )
{
throw new UnsupportedOperationException();
return new WithZone( zone );
}

@Override
Expand All @@ -76,4 +76,38 @@ public FakeClock forward( long delta, TimeUnit unit )
nanoTime += unit.toNanos( delta );
return this;
}

private class WithZone extends Clock
{
private final ZoneId zone;

WithZone( ZoneId zone )
{
this.zone = zone;
}

@Override
public ZoneId getZone()
{
return zone;
}

@Override
public Clock withZone( ZoneId zone )
{
return new WithZone( zone );
}

@Override
public long millis()
{
return FakeClock.this.millis();
}

@Override
public Instant instant()
{
return FakeClock.this.instant();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,19 @@ public ZoneId getZone()
@Override
public Clock withZone( ZoneId zone )
{
throw new UnsupportedOperationException( "Zone update is not supported." );
return Clock.system( zone ); // the users of this method do not need a NanoClock
}

@Override
public Instant instant()
{
return Instant.now();
return Instant.ofEpochMilli( millis() );
}

@Override
public long millis()
{
return System.currentTimeMillis();
}

public long nanos()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package org.neo4j.cypher.internal.codegen;

import java.lang.reflect.Array;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -36,8 +38,14 @@
import org.neo4j.kernel.impl.core.EmbeddedProxySPI;
import org.neo4j.values.AnyValueWriter;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.DateTimeValue;
import org.neo4j.values.storable.DateValue;
import org.neo4j.values.storable.DurationValue;
import org.neo4j.values.storable.LocalDateTimeValue;
import org.neo4j.values.storable.LocalTimeValue;
import org.neo4j.values.storable.TextArray;
import org.neo4j.values.storable.TextValue;
import org.neo4j.values.storable.TimeValue;
import org.neo4j.values.storable.Values;
import org.neo4j.values.virtual.RelationshipValue;
import org.neo4j.values.virtual.MapValue;
Expand Down Expand Up @@ -348,6 +356,48 @@ public void writePoint( CoordinateReferenceSystem crs, double[] coordinate ) thr
writeValue( Values.pointValue( crs, coordinate ) );
}

@Override
public void writeDuration( long months, long days, long seconds, int nanos ) throws RuntimeException
{
writeValue( DurationValue.duration( months, days, seconds, nanos ) );
}

@Override
public void writeDate( long epochDay ) throws RuntimeException
{
writeValue( DateValue.epochDate( epochDay ) );
}

@Override
public void writeLocalTime( long nanoOfDay ) throws RuntimeException
{
writeValue( LocalTimeValue.localTime( nanoOfDay ) );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException
{
writeValue( TimeValue.time( nanosOfDayUTC, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException
{
writeValue( LocalDateTimeValue.localDateTime( epochSecond, nano ) );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException
{
writeValue( DateTimeValue.datetime( epochSecondUTC, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException
{
writeValue( DateTimeValue.datetime( epochSecondUTC, nano, ZoneId.of( zoneId ) ) );
}

private interface Writer
{
void write( Object value );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon
override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] =
translateIterator(inner.callDbmsProcedure(name, args, allowed))

override def callFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) =
override def callFunction(name: QualifiedName, args: Seq[AnyValue], allowed: Array[String]) =
translateException(inner.callFunction(name, args, allowed))


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import org.neo4j.kernel.api.proc.{Neo4jTypes, QualifiedName => KernelQualifiedNa
import org.neo4j.kernel.api.schema.SchemaDescriptorFactory
import org.neo4j.kernel.api.schema.constaints.ConstraintDescriptor
import org.neo4j.kernel.api.schema.index.{IndexDescriptor => KernelIndexDescriptor}
import org.neo4j.kernel.impl.proc.Neo4jValue
import org.neo4j.kernel.impl.proc.DefaultParameterValue
import org.neo4j.procedure.Mode

import scala.collection.JavaConverters._
Expand Down Expand Up @@ -179,7 +179,7 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapper, logger: Inter
"Unable to execute procedure, because it requires an unrecognized execution mode: " + mode.name(), null )
}

private def asCypherValue(neo4jValue: Neo4jValue) = CypherValue(neo4jValue.value, asCypherType(neo4jValue.neo4jType()))
private def asCypherValue(neo4jValue: DefaultParameterValue) = CypherValue(neo4jValue.value, asCypherType(neo4jValue.neo4jType()))

private def asCypherType(neoType: AnyType): CypherType = neoType match {
case Neo4jTypes.NTString => symbols.CTString
Expand All @@ -195,6 +195,12 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapper, logger: Inter
case Neo4jTypes.NTGeometry => symbols.CTGeometry
case Neo4jTypes.NTMap => symbols.CTMap
case Neo4jTypes.NTAny => symbols.CTAny
case Neo4jTypes.NTDateTime => symbols.CTAny
case Neo4jTypes.NTLocalDateTime => symbols.CTAny
case Neo4jTypes.NTDate => symbols.CTAny
case Neo4jTypes.NTTime => symbols.CTAny
case Neo4jTypes.NTLocalTime => symbols.CTAny
case Neo4jTypes.NTDuration => symbols.CTAny
}

override def notificationLogger(): InternalNotificationLogger = logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import org.neo4j.kernel.api.schema.index.IndexDescriptorFactory
import org.neo4j.kernel.api.{exceptions, _}
import org.neo4j.kernel.impl.core.EmbeddedProxySPI
import org.neo4j.kernel.impl.locking.ResourceTypes
import org.neo4j.kernel.impl.util.ValueUtils
import org.neo4j.values.AnyValue
import org.neo4j.values.storable.Values

import scala.collection.Iterator
Expand Down Expand Up @@ -593,7 +595,7 @@ final class TransactionBoundQueryContext(txContext: TransactionalContextWrapper)
}

type KernelProcedureCall = (KernelQualifiedName, Array[AnyRef]) => RawIterator[Array[AnyRef], ProcedureException]
type KernelFunctionCall = (KernelQualifiedName, Array[AnyRef]) => AnyRef
type KernelFunctionCall = (KernelQualifiedName, Array[AnyValue]) => AnyValue

private def shouldElevate(allowed: Array[String]): Boolean = {
// We have to be careful with elevation, since we cannot elevate permissions in a nested procedure call
Expand Down Expand Up @@ -646,17 +648,18 @@ final class TransactionBoundQueryContext(txContext: TransactionalContextWrapper)
override def callFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = {
val call: KernelFunctionCall =
if (shouldElevate(allowed))
txContext.statement.procedureCallOperations.functionCallOverride(_, _)
(name, args) => txContext.statement.procedureCallOperations.functionCallOverride(name, args)
else
txContext.statement.procedureCallOperations.functionCall(_, _)
(name, args) => txContext.statement.procedureCallOperations.functionCall(name, args)
callFunction(name, args, call)
}

private def callFunction(name: QualifiedName, args: Seq[Any],
call: KernelFunctionCall) = {
val kn = new KernelQualifiedName(name.namespace.asJava, name.name)
val toArray = args.map(_.asInstanceOf[AnyRef]).toArray
call(kn, toArray)
val argArray = args.map(ValueUtils.of).toArray
val result = call(kn, argArray)
result.map(txContext.statement.procedureCallOperations.valueMapper)
}

override def isGraphKernelResultValue(v: Any): Boolean = v.isInstanceOf[PropertyContainer] || v.isInstanceOf[Path]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import org.neo4j.kernel.api.proc.Neo4jTypes.AnyType
import org.neo4j.kernel.api.proc.{Neo4jTypes, QualifiedName => KernelQualifiedName}
import org.neo4j.kernel.api.schema.SchemaDescriptorFactory
import org.neo4j.kernel.api.schema.index.{IndexDescriptor => KernelIndexDescriptor}
import org.neo4j.kernel.impl.proc.Neo4jValue
import org.neo4j.kernel.impl.proc.DefaultParameterValue
import org.neo4j.procedure.Mode

import scala.collection.JavaConverters._
Expand Down Expand Up @@ -172,7 +172,7 @@ class TransactionBoundPlanContext(readOperationsSupplier: () => ReadOperations,
"Unable to execute procedure, because it requires an unrecognized execution mode: " + mode.name(), null)
}

private def asCypherValue(neo4jValue: Neo4jValue) = CypherValue(neo4jValue.value,
private def asCypherValue(neo4jValue: DefaultParameterValue) = CypherValue(neo4jValue.value,
asCypherType(neo4jValue.neo4jType()))

private def asCypherType(neoType: AnyType): CypherType = neoType match {
Expand All @@ -189,6 +189,12 @@ class TransactionBoundPlanContext(readOperationsSupplier: () => ReadOperations,
case Neo4jTypes.NTGeometry => symbols.CTGeometry
case Neo4jTypes.NTMap => symbols.CTMap
case Neo4jTypes.NTAny => symbols.CTAny
case Neo4jTypes.NTDateTime => symbols.CTAny
case Neo4jTypes.NTLocalDateTime => symbols.CTAny
case Neo4jTypes.NTDate => symbols.CTAny
case Neo4jTypes.NTTime => symbols.CTAny
case Neo4jTypes.NTLocalTime => symbols.CTAny
case Neo4jTypes.NTDuration => symbols.CTAny
}

override def notificationLogger(): InternalNotificationLogger = logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,24 @@ class LogicalPlanConverterTest extends FunSuite with Matchers {
}
}

test("should convert function call with 'null' default value") {
val allowed = Array.empty[String] // this is passed through as the same instance - so shared
val name3_3 = plansV3_3.QualifiedName(Seq.empty, "foo")
val call3_3 = compilerV3_3.ast.ResolvedFunctionInvocation(name3_3,
Some(plansV3_3.UserFunctionSignature(name3_3, Vector(plansV3_3.FieldSignature("input", symbolsV3_3.CTAny,
default = Some(plansV3_3.CypherValue(null, symbolsV3_3.CTAny)))),
symbolsV3_3.CTAny, None, allowed, None, isAggregate = false)), Vector())(InputPositionV3_3(1, 2, 3))

val name3_4 = plansV3_4.QualifiedName(Seq.empty, "foo")
val call3_4 = plansV3_4.ResolvedFunctionInvocation(name3_4,
Some(plansV3_4.UserFunctionSignature(name3_4, Vector(plansV3_4.FieldSignature("input", symbolsV3_4.CTAny,
default = Some(plansV3_4.CypherValue(null, symbolsV3_4.CTAny)))),
symbolsV3_4.CTAny, None, allowed, None, isAggregate = false)), Vector())(InputPosition(1, 2, 3))

val rewritten = LogicalPlanConverter.convertExpression[plansV3_4.ResolvedFunctionInvocation](call3_3, new Solveds, new Cardinalities)
rewritten should be(call3_4)
}

private def argumentProvider[T <: AnyRef](clazz: Class[T]): T = {
val variable = astV3_3.Variable("n")(pos3_3)
val value = clazz.getSimpleName match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
*/
package org.neo4j.cypher.internal.runtime.interpreted

import java.time.temporal.{Temporal, TemporalAmount}
import java.time.{ZoneId, ZoneOffset}

import org.neo4j.cypher.internal.util.v3_4.CypherTypeException
import org.neo4j.graphdb.spatial.Point
import org.neo4j.values.storable.{ArrayValue, _}
Expand Down Expand Up @@ -137,6 +140,18 @@ object CastSupport {
transform(new ArrayConverterWriter(classOf[PointValue], a => Values.pointArray(a.asInstanceOf[Array[PointValue]]))))
case _: Point => Converter(
transform(new ArrayConverterWriter(classOf[Point], a => Values.pointArray(a.asInstanceOf[Array[Point]]))))
case _: DurationValue => Converter(
transform(new ArrayConverterWriter(classOf[DurationValue], a => Values.durationArray(a.asInstanceOf[Array[TemporalAmount]]))))
case _: DateValue => Converter(
transform(new ArrayConverterWriter(classOf[DateValue], a => Values.temporalArray(a.asInstanceOf[Array[Temporal]]))))
case _: LocalTimeValue => Converter(
transform(new ArrayConverterWriter(classOf[LocalTimeValue], a => Values.temporalArray(a.asInstanceOf[Array[Temporal]]))))
case _: TimeValue => Converter(
transform(new ArrayConverterWriter(classOf[TimeValue], a => Values.temporalArray(a.asInstanceOf[Array[Temporal]]))))
case _: LocalDateTimeValue => Converter(
transform(new ArrayConverterWriter(classOf[LocalDateTimeValue], a => Values.temporalArray(a.asInstanceOf[Array[Temporal]]))))
case _: DateTimeValue => Converter(
transform(new ArrayConverterWriter(classOf[DateTimeValue], a => Values.temporalArray(a.asInstanceOf[Array[Temporal]]))))
case _ => throw new CypherTypeException("Property values can only be of primitive types or arrays thereof")
}

Expand Down Expand Up @@ -215,6 +230,27 @@ object CastSupport {

override def writePoint(crs: CoordinateReferenceSystem, coordinate: Array[Double]): Unit =
write(Values.pointValue(crs, coordinate: _*))

override def writeDuration(months: Long, days: Long, seconds: Long, nanos: Int): Unit =
write(DurationValue.duration(months, days, seconds, nanos))

override def writeDate(epochDay: Long): Unit =
write(DateValue.epochDate(epochDay).asObject())

override def writeLocalTime(nanoOfDay: Long): Unit =
write(LocalTimeValue.localTime(nanoOfDay).asObject())

override def writeTime(nanosOfDayUTC: Long, offsetSeconds: Int): Unit =
write(TimeValue.time(nanosOfDayUTC, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject())

override def writeLocalDateTime(epochSecond: Long, nano: Int): Unit =
write(LocalDateTimeValue.localDateTime(epochSecond,nano).asObject())

override def writeDateTime(epochSecondUTC: Long, nano: Int, offsetSeconds: Int): Unit =
write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject())

override def writeDateTime(epochSecondUTC: Long, nano: Int, zoneId: String): Unit =
write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneId.of(zoneId)).asObject())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ abstract class DelegatingQueryContext(val inner: QueryContext) extends QueryCont
override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) =
inner.callDbmsProcedure(name, args, allowed)

override def callFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) =
override def callFunction(name: QualifiedName, args: Seq[AnyValue], allowed: Array[String]) =
singleDbHit(inner.callFunction(name, args, allowed))

override def aggregateFunction(name: QualifiedName,
Expand Down

0 comments on commit 7b0eb1c

Please sign in to comment.