Skip to content

Commit

Permalink
Propaget null in temporal functions
Browse files Browse the repository at this point in the history
Since null was used as a default arguments, we had to introduce
a different default argument to be able to distinguish the null case.
  • Loading branch information
sherfert committed Jul 24, 2018
1 parent cb7b886 commit fc3e0eb
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 252 deletions.
Expand Up @@ -23,6 +23,7 @@
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;


Expand Down Expand Up @@ -96,8 +97,8 @@ private static class FromEpoch implements CallableUserFunction
private static final String DESCRIPTION = private static final String DESCRIPTION =
"Create a DateTime given the seconds and nanoseconds since the start of the epoch."; "Create a DateTime given the seconds and nanoseconds since the start of the epoch.";
private static final List<FieldSignature> SIGNATURE = Arrays.asList( private static final List<FieldSignature> SIGNATURE = Arrays.asList(
inputField( "seconds", Neo4jTypes.NTAny ), inputField( "seconds", Neo4jTypes.NTNumber ),
inputField( "nanoseconds", Neo4jTypes.NTAny ) ); inputField( "nanoseconds", Neo4jTypes.NTNumber ) );
private final UserFunctionSignature signature; private final UserFunctionSignature signature;


private FromEpoch() private FromEpoch()
Expand Down Expand Up @@ -135,8 +136,7 @@ private static class FromEpochMillis implements CallableUserFunction
{ {
private static final String DESCRIPTION = private static final String DESCRIPTION =
"Create a DateTime given the milliseconds since the start of the epoch."; "Create a DateTime given the milliseconds since the start of the epoch.";
private static final List<FieldSignature> SIGNATURE = Arrays.asList( private static final List<FieldSignature> SIGNATURE = Collections.singletonList( inputField( "milliseconds", Neo4jTypes.NTNumber ) );
inputField( "milliseconds", Neo4jTypes.NTAny ) );
private final UserFunctionSignature signature; private final UserFunctionSignature signature;


private FromEpochMillis() private FromEpochMillis()
Expand Down
Expand Up @@ -43,6 +43,7 @@
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;


import static org.neo4j.internal.kernel.api.procs.FieldSignature.inputField; import static org.neo4j.internal.kernel.api.procs.FieldSignature.inputField;
import static org.neo4j.values.storable.Values.NO_VALUE;


@Description( "Construct a Duration value." ) @Description( "Construct a Duration value." )
class DurationFunction implements CallableUserFunction class DurationFunction implements CallableUserFunction
Expand Down Expand Up @@ -71,15 +72,23 @@ public UserFunctionSignature signature()
} }


@Override @Override
public DurationValue apply( Context ctx, AnyValue[] input ) throws ProcedureException public AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException
{ {
if ( input != null && input.length == 1 ) if ( input == null )
{ {
if ( input[0] instanceof TextValue ) return NO_VALUE;
}
else if ( input.length == 1 )
{
if ( input[0] == NO_VALUE || input[0] == null )
{
return NO_VALUE;
}
else if ( input[0] instanceof TextValue )
{ {
return DurationValue.parse( (TextValue) input[0] ); return DurationValue.parse( (TextValue) input[0] );
} }
if ( input[0] instanceof MapValue ) else if ( input[0] instanceof MapValue )
{ {
MapValue map = (MapValue) input[0]; MapValue map = (MapValue) input[0];
return DurationValue.build( map ); return DurationValue.build( map );
Expand Down Expand Up @@ -139,7 +148,11 @@ public UserFunctionSignature signature()
@Override @Override
public AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException public AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException
{ {
if ( input != null && input.length == 2 ) if ( input == null || (input.length == 2 && (input[0] == NO_VALUE || input[0] == null) || input[1] == NO_VALUE || input[1] == null) )
{
return NO_VALUE;
}
else if ( input.length == 2 )
{ {
if ( input[0] instanceof TemporalValue && input[1] instanceof TemporalValue ) if ( input[0] instanceof TemporalValue && input[1] instanceof TemporalValue )
{ {
Expand Down
Expand Up @@ -29,6 +29,7 @@
import java.util.function.Supplier; import java.util.function.Supplier;


import org.neo4j.internal.kernel.api.exceptions.ProcedureException; import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.procs.DefaultParameterValue;
import org.neo4j.internal.kernel.api.procs.FieldSignature; import org.neo4j.internal.kernel.api.procs.FieldSignature;
import org.neo4j.internal.kernel.api.procs.Neo4jTypes; import org.neo4j.internal.kernel.api.procs.Neo4jTypes;
import org.neo4j.internal.kernel.api.procs.QualifiedName; import org.neo4j.internal.kernel.api.procs.QualifiedName;
Expand All @@ -41,9 +42,9 @@
import org.neo4j.kernel.impl.proc.Procedures; import org.neo4j.kernel.impl.proc.Procedures;
import org.neo4j.procedure.Description; import org.neo4j.procedure.Description;
import org.neo4j.values.AnyValue; import org.neo4j.values.AnyValue;
import org.neo4j.values.storable.IntegralValue;
import org.neo4j.values.storable.TemporalValue; import org.neo4j.values.storable.TemporalValue;
import org.neo4j.values.storable.TextValue; import org.neo4j.values.storable.TextValue;
import org.neo4j.values.storable.Values;
import org.neo4j.values.virtual.MapValue; import org.neo4j.values.virtual.MapValue;


import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
Expand All @@ -55,6 +56,10 @@


public abstract class TemporalFunction<T extends AnyValue> implements CallableUserFunction public abstract class TemporalFunction<T extends AnyValue> implements CallableUserFunction
{ {
private static final String DEFAULT_TEMPORAL_ARGUMENT = "DEFAULT_TEMPORAL_ARGUMENT";
private static final TextValue DEFAULT_TEMPORAL_ARGUMENT_VALUE = Values.stringValue( DEFAULT_TEMPORAL_ARGUMENT );
private static final DefaultParameterValue DEFAULT_PARAMETER_VALUE = new DefaultParameterValue( DEFAULT_TEMPORAL_ARGUMENT, Neo4jTypes.NTAny );

public static void registerTemporalFunctions( Procedures procedures, ProcedureConfig procedureConfig ) throws ProcedureException public static void registerTemporalFunctions( Procedures procedures, ProcedureConfig procedureConfig ) throws ProcedureException
{ {
Supplier<ZoneId> defaultZone = procedureConfig::getDefaultTemporalTimeZone; Supplier<ZoneId> defaultZone = procedureConfig::getDefaultTemporalTimeZone;
Expand Down Expand Up @@ -84,8 +89,7 @@ public static void registerTemporalFunctions( Procedures procedures, ProcedureCo


protected abstract T truncate( TemporalUnit unit, TemporalValue input, MapValue fields, Supplier<ZoneId> defaultZone ); protected abstract T truncate( TemporalUnit unit, TemporalValue input, MapValue fields, Supplier<ZoneId> defaultZone );


private static final List<FieldSignature> INPUT_SIGNATURE = singletonList( inputField( private static final List<FieldSignature> INPUT_SIGNATURE = singletonList( inputField( "input", Neo4jTypes.NTAny, DEFAULT_PARAMETER_VALUE ) );
"input", Neo4jTypes.NTAny, nullValue( Neo4jTypes.NTAny ) ) );
private static final String[] ALLOWED = {}; private static final String[] ALLOWED = {};


private final UserFunctionSignature signature; private final UserFunctionSignature signature;
Expand Down Expand Up @@ -118,28 +122,6 @@ private static String basename( Class<? extends TemporalFunction> function )
return function.getSimpleName().replace( "Function", "" ); return function.getSimpleName().replace( "Function", "" );
} }


static int anInt( String name, AnyValue value )
{
if ( value instanceof IntegralValue )
{
long v = ((IntegralValue) value).longValue();
if ( v <= Integer.MAX_VALUE && v >= Integer.MIN_VALUE )
{
return (int) v;
}
}
throw new IllegalArgumentException( name + " should be an int, not: " + value );
}

static String aString( String name, AnyValue value )
{
if ( value instanceof TextValue )
{
return ((TextValue) value).stringValue();
}
throw new IllegalArgumentException( name + " should be a string, not: " + value );
}

void registerMore( Procedures procedures ) throws ProcedureException void registerMore( Procedures procedures ) throws ProcedureException
{ {
// Empty by default // Empty by default
Expand All @@ -152,9 +134,13 @@ public final UserFunctionSignature signature()
} }


@Override @Override
public final T apply( Context ctx, AnyValue[] input ) throws ProcedureException public final AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException
{ {
if ( input == null || input.length == 0 || input[0] == NO_VALUE || input[0] == null ) if ( input == null || (input.length > 0 && (input[0] == NO_VALUE || input[0] == null)) )
{
return NO_VALUE;
}
else if ( input.length == 0 || input[0].equals( DEFAULT_TEMPORAL_ARGUMENT_VALUE ) )
{ {
return now( ctx.get( DEFAULT_CLOCK ), null, defaultZone ); return now( ctx.get( DEFAULT_CLOCK ), null, defaultZone );
} }
Expand Down Expand Up @@ -220,13 +206,12 @@ public final UserFunctionSignature signature()
} }


@Override @Override
public abstract T apply( Context ctx, AnyValue[] input ) throws ProcedureException; public abstract AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException;
} }


private static class Now<T extends AnyValue> extends SubFunction<T> private static class Now<T extends AnyValue> extends SubFunction<T>
{ {
private static final List<FieldSignature> SIGNATURE = singletonList( inputField( private static final List<FieldSignature> SIGNATURE = singletonList( inputField( "timezone", Neo4jTypes.NTAny, DEFAULT_PARAMETER_VALUE ) );
"timezone", Neo4jTypes.NTAny, nullValue( Neo4jTypes.NTAny ) ) );
private final Key<Clock> key; private final Key<Clock> key;


Now( TemporalFunction<T> function, String clock ) Now( TemporalFunction<T> function, String clock )
Expand All @@ -251,10 +236,13 @@ private static class Now<T extends AnyValue> extends SubFunction<T>
} }


@Override @Override
public T apply( Context ctx, AnyValue[] input ) throws ProcedureException public AnyValue apply( Context ctx, AnyValue[] input ) throws ProcedureException
{ {
if ( input == null || input.length == 0 || if ( input == null || (input.length > 0 && (input[0] == NO_VALUE || input[0] == null)) )
((input[0] == NO_VALUE || input[0] == null) && input.length == 1) ) {
return NO_VALUE;
}
else if ( input.length == 0 || input[0].equals( DEFAULT_TEMPORAL_ARGUMENT_VALUE ) )
{ {
return function.now( ctx.get( key ), null, function.defaultZone ); return function.now( ctx.get( key ), null, function.defaultZone );
} }
Expand Down
Expand Up @@ -142,6 +142,7 @@ Feature "TemporalTruncateAcceptance": Scenario "Should truncate datetime to micr
Feature "TemporalTruncateAcceptance": Scenario "Should truncate localdatetime to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate localdatetime to microsecond"
Feature "TemporalTruncateAcceptance": Scenario "Should truncate time to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate time to microsecond"
Feature "TemporalTruncateAcceptance": Scenario "Should truncate localtime to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate localtime to microsecond"
Feature "TemporalCreateAcceptance": Scenario "Should propagate null"
Feature "TemporalCreateAcceptance": Scenario "Should construct week date" Feature "TemporalCreateAcceptance": Scenario "Should construct week date"
Feature "TemporalCreateAcceptance": Scenario "Should construct week localdatetime" Feature "TemporalCreateAcceptance": Scenario "Should construct week localdatetime"
Feature "TemporalCreateAcceptance": Scenario "Should construct week datetime" Feature "TemporalCreateAcceptance": Scenario "Should construct week datetime"
Expand Down Expand Up @@ -176,3 +177,4 @@ Feature "DurationBetweenAcceptance": Scenario "Should handle large durations"
Feature "DurationBetweenAcceptance": Scenario "Should handle when seconds and subseconds have different signs" Feature "DurationBetweenAcceptance": Scenario "Should handle when seconds and subseconds have different signs"
Feature "DurationBetweenAcceptance": Scenario "Should compute durations with no difference" Feature "DurationBetweenAcceptance": Scenario "Should compute durations with no difference"
Feature "DurationBetweenAcceptance": Scenario "Should split between boundaries correctly" Feature "DurationBetweenAcceptance": Scenario "Should split between boundaries correctly"
Feature "DurationBetweenAcceptance": Scenario "Should propagate null"
Expand Up @@ -278,6 +278,7 @@ Feature "TemporalTruncateAcceptance": Scenario "Should truncate datetime to micr
Feature "TemporalTruncateAcceptance": Scenario "Should truncate localdatetime to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate localdatetime to microsecond"
Feature "TemporalTruncateAcceptance": Scenario "Should truncate time to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate time to microsecond"
Feature "TemporalTruncateAcceptance": Scenario "Should truncate localtime to microsecond" Feature "TemporalTruncateAcceptance": Scenario "Should truncate localtime to microsecond"
Feature "TemporalCreateAcceptance": Scenario "Should propagate null"
Feature "TemporalCreateAcceptance": Scenario "Should construct week date" Feature "TemporalCreateAcceptance": Scenario "Should construct week date"
Feature "TemporalCreateAcceptance": Scenario "Should construct week localdatetime" Feature "TemporalCreateAcceptance": Scenario "Should construct week localdatetime"
Feature "TemporalCreateAcceptance": Scenario "Should construct week datetime" Feature "TemporalCreateAcceptance": Scenario "Should construct week datetime"
Expand Down Expand Up @@ -305,3 +306,4 @@ Feature "DurationBetweenAcceptance": Scenario "Should handle large durations"
Feature "DurationBetweenAcceptance": Scenario "Should handle when seconds and subseconds have different signs" Feature "DurationBetweenAcceptance": Scenario "Should handle when seconds and subseconds have different signs"
Feature "DurationBetweenAcceptance": Scenario "Should compute durations with no difference" Feature "DurationBetweenAcceptance": Scenario "Should compute durations with no difference"
Feature "DurationBetweenAcceptance": Scenario "Should split between boundaries correctly" Feature "DurationBetweenAcceptance": Scenario "Should split between boundaries correctly"
Feature "DurationBetweenAcceptance": Scenario "Should propagate null"

0 comments on commit fc3e0eb

Please sign in to comment.