Skip to content

Commit

Permalink
Configuration option to set the default time zone for temporal values.
Browse files Browse the repository at this point in the history
  • Loading branch information
sherfert committed May 4, 2018
1 parent aa111f6 commit 1d1ac2c
Show file tree
Hide file tree
Showing 17 changed files with 237 additions and 50 deletions.
Expand Up @@ -115,8 +115,8 @@ public void doImport() throws IOException
null, false, databaseConfig, storeDir, highIO ) ); null, false, databaseConfig, storeDir, highIO ) );


// Extract the default time zone from the database configuration // Extract the default time zone from the database configuration
LogTimeZone dbTimeZone = databaseConfig.get( GraphDatabaseSettings.db_timezone ); ZoneId dbTimeZone = databaseConfig.get( GraphDatabaseSettings.db_temporal_timezone );
Supplier<ZoneId> defaultTimeZone = () -> dbTimeZone.getZoneId(); Supplier<ZoneId> defaultTimeZone = () -> dbTimeZone;


CsvInput input = new CsvInput( CsvInput input = new CsvInput(
nodeData( inputEncoding, nodesFiles ), defaultFormatNodeFileHeader( defaultTimeZone ), nodeData( inputEncoding, nodesFiles ), defaultFormatNodeFileHeader( defaultTimeZone ),
Expand Down
Expand Up @@ -21,6 +21,8 @@


import java.io.File; import java.io.File;
import java.time.Duration; import java.time.Duration;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List; import java.util.List;


import org.neo4j.configuration.Description; import org.neo4j.configuration.Description;
Expand All @@ -45,6 +47,7 @@
import org.neo4j.kernel.configuration.ssl.SslPolicyConfigValidator; import org.neo4j.kernel.configuration.ssl.SslPolicyConfigValidator;
import org.neo4j.logging.Level; import org.neo4j.logging.Level;
import org.neo4j.logging.LogTimeZone; import org.neo4j.logging.LogTimeZone;
import org.neo4j.values.storable.DateTimeValue;


import static org.neo4j.kernel.configuration.Settings.BOOLEAN; import static org.neo4j.kernel.configuration.Settings.BOOLEAN;
import static org.neo4j.kernel.configuration.Settings.BYTES; import static org.neo4j.kernel.configuration.Settings.BYTES;
Expand Down Expand Up @@ -382,7 +385,7 @@ public class GraphDatabaseSettings implements LoadableConfig
public static final Setting<Level> store_internal_log_level = setting( "dbms.logs.debug.level", public static final Setting<Level> store_internal_log_level = setting( "dbms.logs.debug.level",
options( Level.class ), "INFO" ); options( Level.class ), "INFO" );


@Description( "Database timezone." ) @Description( "Database timezone. This setting influences which timezone the logs use." )
public static final Setting<LogTimeZone> db_timezone = public static final Setting<LogTimeZone> db_timezone =
setting( "dbms.db.timezone", options( LogTimeZone.class ), LogTimeZone.UTC.name() ); setting( "dbms.db.timezone", options( LogTimeZone.class ), LogTimeZone.UTC.name() );


Expand All @@ -392,6 +395,11 @@ public class GraphDatabaseSettings implements LoadableConfig
public static final Setting<LogTimeZone> log_timezone = public static final Setting<LogTimeZone> log_timezone =
setting( "dbms.logs.timezone", options( LogTimeZone.class ), LogTimeZone.UTC.name() ); setting( "dbms.logs.timezone", options( LogTimeZone.class ), LogTimeZone.UTC.name() );


@Description( "Database timezone for temporal functions. All Time and DateTime values that are created without " +
"an explicit timezone will use the configured default timezone." )
public static final Setting<ZoneId> db_temporal_timezone =
setting( "db.temporal.timezone", DateTimeValue::parseZoneOffsetOrZoneName, ZoneOffset.UTC.toString() );

@Description( "Maximum time to wait for active transaction completion when rotating counts store" ) @Description( "Maximum time to wait for active transaction completion when rotating counts store" )
@Internal @Internal
public static final Setting<Duration> counts_store_rotation_timeout = public static final Setting<Duration> counts_store_rotation_timeout =
Expand Down
Expand Up @@ -262,10 +262,11 @@ private Procedures setupProcedures( PlatformModule platform, EditionModule editi
Log internalLog = platform.logging.getInternalLog( Procedures.class ); Log internalLog = platform.logging.getInternalLog( Procedures.class );
EmbeddedProxySPI proxySPI = platform.dependencies.resolveDependency( EmbeddedProxySPI.class ); EmbeddedProxySPI proxySPI = platform.dependencies.resolveDependency( EmbeddedProxySPI.class );


ProcedureConfig procedureConfig = new ProcedureConfig( platform.config );
Procedures procedures = new Procedures( proxySPI, Procedures procedures = new Procedures( proxySPI,
new SpecialBuiltInProcedures( Version.getNeo4jVersion(), new SpecialBuiltInProcedures( Version.getNeo4jVersion(),
platform.databaseInfo.edition.toString() ), platform.databaseInfo.edition.toString() ),
pluginDir, internalLog, new ProcedureConfig( platform.config ) ); pluginDir, internalLog, procedureConfig );
platform.life.add( procedures ); platform.life.add( procedures );
platform.dependencies.satisfyDependency( procedures ); platform.dependencies.satisfyDependency( procedures );


Expand Down Expand Up @@ -300,7 +301,7 @@ private Procedures setupProcedures( PlatformModule platform, EditionModule editi
// Edition procedures // Edition procedures
try try
{ {
editionModule.registerProcedures( procedures ); editionModule.registerProcedures( procedures, procedureConfig );
} }
catch ( KernelException e ) catch ( KernelException e )
{ {
Expand Down
Expand Up @@ -43,6 +43,7 @@
import org.neo4j.kernel.impl.locking.Locks; import org.neo4j.kernel.impl.locking.Locks;
import org.neo4j.kernel.impl.locking.StatementLocksFactory; import org.neo4j.kernel.impl.locking.StatementLocksFactory;
import org.neo4j.kernel.impl.logging.LogService; import org.neo4j.kernel.impl.logging.LogService;
import org.neo4j.kernel.impl.proc.ProcedureConfig;
import org.neo4j.kernel.impl.proc.Procedures; import org.neo4j.kernel.impl.proc.Procedures;
import org.neo4j.kernel.impl.storageengine.impl.recordstorage.id.BufferedIdController; import org.neo4j.kernel.impl.storageengine.impl.recordstorage.id.BufferedIdController;
import org.neo4j.kernel.impl.storageengine.impl.recordstorage.id.DefaultIdController; import org.neo4j.kernel.impl.storageengine.impl.recordstorage.id.DefaultIdController;
Expand Down Expand Up @@ -78,13 +79,13 @@ public abstract class EditionModule
private static final boolean safeIdBuffering = FeatureToggles.flag( private static final boolean safeIdBuffering = FeatureToggles.flag(
EditionModule.class, "safeIdBuffering", true ); EditionModule.class, "safeIdBuffering", true );


void registerProcedures( Procedures procedures ) throws KernelException void registerProcedures( Procedures procedures, ProcedureConfig procedureConfig ) throws KernelException
{ {
procedures.registerProcedure( org.neo4j.kernel.builtinprocs.BuiltInProcedures.class ); procedures.registerProcedure( org.neo4j.kernel.builtinprocs.BuiltInProcedures.class );
procedures.registerProcedure( org.neo4j.kernel.builtinprocs.TokenProcedures.class ); procedures.registerProcedure( org.neo4j.kernel.builtinprocs.TokenProcedures.class );
procedures.registerProcedure( org.neo4j.kernel.builtinprocs.BuiltInDbmsProcedures.class ); procedures.registerProcedure( org.neo4j.kernel.builtinprocs.BuiltInDbmsProcedures.class );
procedures.registerBuiltInFunctions( org.neo4j.kernel.builtinprocs.BuiltInFunctions.class ); procedures.registerBuiltInFunctions( org.neo4j.kernel.builtinprocs.BuiltInFunctions.class );
registerTemporalFunctions( procedures ); registerTemporalFunctions( procedures, procedureConfig );


registerEditionSpecificProcedures( procedures ); registerEditionSpecificProcedures( procedures );
} }
Expand Down
Expand Up @@ -19,6 +19,7 @@
*/ */
package org.neo4j.kernel.impl.proc; package org.neo4j.kernel.impl.proc;


import java.time.ZoneId;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
Expand All @@ -29,6 +30,7 @@
import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Config;


import static java.time.ZoneOffset.UTC;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;


public class ProcedureConfig public class ProcedureConfig
Expand All @@ -45,13 +47,15 @@ public class ProcedureConfig
private final List<ProcMatcher> matchers; private final List<ProcMatcher> matchers;
private final List<Pattern> accessPatterns; private final List<Pattern> accessPatterns;
private final List<Pattern> whiteList; private final List<Pattern> whiteList;
private final ZoneId defaultTemporalTimeZone;


private ProcedureConfig() private ProcedureConfig()
{ {
this.defaultValue = ""; this.defaultValue = "";
this.matchers = Collections.emptyList(); this.matchers = Collections.emptyList();
this.accessPatterns = Collections.emptyList(); this.accessPatterns = Collections.emptyList();
this.whiteList = Collections.singletonList( compilePattern( "*" ) ); this.whiteList = Collections.singletonList( compilePattern( "*" ) );
this.defaultTemporalTimeZone = UTC;
} }


public ProcedureConfig( Config config ) public ProcedureConfig( Config config )
Expand All @@ -78,6 +82,7 @@ public ProcedureConfig( Config config )
this.whiteList = this.whiteList =
parseMatchers( GraphDatabaseSettings.procedure_whitelist.name(), config, PROCEDURE_DELIMITER, parseMatchers( GraphDatabaseSettings.procedure_whitelist.name(), config, PROCEDURE_DELIMITER,
ProcedureConfig::compilePattern ); ProcedureConfig::compilePattern );
this.defaultTemporalTimeZone = config.get( GraphDatabaseSettings.db_temporal_timezone );
} }


private <T> List<T> parseMatchers( String configName, Config config, String delimiter, Function<String,T> private <T> List<T> parseMatchers( String configName, Config config, String delimiter, Function<String,T>
Expand Down Expand Up @@ -133,6 +138,11 @@ private String[] getDefaultValue()


static final ProcedureConfig DEFAULT = new ProcedureConfig(); static final ProcedureConfig DEFAULT = new ProcedureConfig();


public ZoneId getDefaultTemporalTimeZone()
{
return defaultTemporalTimeZone;
}

private static class ProcMatcher private static class ProcMatcher
{ {
private final Pattern pattern; private final Pattern pattern;
Expand Down
Expand Up @@ -36,15 +36,15 @@
@Description( "Create a Date instant." ) @Description( "Create a Date instant." )
class DateFunction extends TemporalFunction<DateValue> class DateFunction extends TemporalFunction<DateValue>
{ {
DateFunction() DateFunction( Supplier<ZoneId> defaultZone )
{ {
super( NTDate ); super( NTDate, defaultZone );
} }


@Override @Override
protected DateValue now( Clock clock, String timezone ) protected DateValue now( Clock clock, String timezone, Supplier<ZoneId> defaultZone )
{ {
return timezone == null ? DateValue.now( clock ) : DateValue.now( clock, timezone ); return timezone == null ? DateValue.now( clock, defaultZone ) : DateValue.now( clock, timezone );
} }


@Override @Override
Expand Down
Expand Up @@ -49,15 +49,15 @@
@Description( "Create a DateTime instant." ) @Description( "Create a DateTime instant." )
class DateTimeFunction extends TemporalFunction<DateTimeValue> class DateTimeFunction extends TemporalFunction<DateTimeValue>
{ {
DateTimeFunction() DateTimeFunction( Supplier<ZoneId> defaultZone )
{ {
super( NTDateTime ); super( NTDateTime, defaultZone );
} }


@Override @Override
protected DateTimeValue now( Clock clock, String timezone ) protected DateTimeValue now( Clock clock, String timezone, Supplier<ZoneId> defaultZone )
{ {
return timezone == null ? DateTimeValue.now( clock ) : DateTimeValue.now( clock, timezone ); return timezone == null ? DateTimeValue.now( clock, defaultZone ) : DateTimeValue.now( clock, timezone );
} }


@Override @Override
Expand Down
Expand Up @@ -36,15 +36,15 @@
@Description( "Create a LocalDateTime instant." ) @Description( "Create a LocalDateTime instant." )
class LocalDateTimeFunction extends TemporalFunction<LocalDateTimeValue> class LocalDateTimeFunction extends TemporalFunction<LocalDateTimeValue>
{ {
LocalDateTimeFunction() LocalDateTimeFunction( Supplier<ZoneId> defaultZone )
{ {
super( NTLocalDateTime ); super( NTLocalDateTime, defaultZone );
} }


@Override @Override
protected LocalDateTimeValue now( Clock clock, String timezone ) protected LocalDateTimeValue now( Clock clock, String timezone, Supplier<ZoneId> defaultZone )
{ {
return timezone == null ? LocalDateTimeValue.now( clock ) : LocalDateTimeValue.now( clock, timezone ); return timezone == null ? LocalDateTimeValue.now( clock, defaultZone ) : LocalDateTimeValue.now( clock, timezone );
} }


@Override @Override
Expand Down
Expand Up @@ -36,15 +36,15 @@
@Description( "Create a LocalTime instant." ) @Description( "Create a LocalTime instant." )
class LocalTimeFunction extends TemporalFunction<LocalTimeValue> class LocalTimeFunction extends TemporalFunction<LocalTimeValue>
{ {
LocalTimeFunction() LocalTimeFunction( Supplier<ZoneId> defaultZone )
{ {
super( NTLocalTime ); super( NTLocalTime, defaultZone );
} }


@Override @Override
protected LocalTimeValue now( Clock clock, String timezone ) protected LocalTimeValue now( Clock clock, String timezone, Supplier<ZoneId> defaultZone )
{ {
return timezone == null ? LocalTimeValue.now( clock ) : LocalTimeValue.now( clock, timezone ); return timezone == null ? LocalTimeValue.now( clock, defaultZone ) : LocalTimeValue.now( clock, timezone );
} }


@Override @Override
Expand Down
Expand Up @@ -37,6 +37,7 @@
import org.neo4j.kernel.api.proc.CallableUserFunction; import org.neo4j.kernel.api.proc.CallableUserFunction;
import org.neo4j.kernel.api.proc.Context; import org.neo4j.kernel.api.proc.Context;
import org.neo4j.kernel.api.proc.Key; import org.neo4j.kernel.api.proc.Key;
import org.neo4j.kernel.impl.proc.ProcedureConfig;
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;
Expand All @@ -54,19 +55,26 @@


public abstract class TemporalFunction<T extends AnyValue> implements CallableUserFunction public abstract class TemporalFunction<T extends AnyValue> implements CallableUserFunction
{ {
public static void registerTemporalFunctions( Procedures procedures ) throws ProcedureException public static void registerTemporalFunctions( Procedures procedures, ProcedureConfig procedureConfig ) throws ProcedureException
{ {
register( new DateTimeFunction(), procedures ); Supplier<ZoneId> defaultZone = procedureConfig::getDefaultTemporalTimeZone;
register( new LocalDateTimeFunction(), procedures ); register( new DateTimeFunction( defaultZone ), procedures );
register( new DateFunction(), procedures ); register( new LocalDateTimeFunction( defaultZone ), procedures );
register( new TimeFunction(), procedures ); register( new DateFunction( defaultZone ), procedures );
register( new LocalTimeFunction(), procedures ); register( new TimeFunction( defaultZone ), procedures );
register( new LocalTimeFunction( defaultZone ), procedures );
DurationFunction.register( procedures ); DurationFunction.register( procedures );
} }


private static final Key<Clock> DEFAULT_CLOCK = Context.STATEMENT_CLOCK; private static final Key<Clock> DEFAULT_CLOCK = Context.STATEMENT_CLOCK;


protected abstract T now( Clock clock, String timezone ); /**
* @param clock the clock to use
* @param timezone an explicit timezone or {@code null}. In the latter case, the defaultZone is used
* @param defaultZone configured default time zone.
* @return the current time/date
*/
protected abstract T now( Clock clock, String timezone, Supplier<ZoneId> defaultZone );


protected abstract T parse( TextValue value, Supplier<ZoneId> defaultZone ); protected abstract T parse( TextValue value, Supplier<ZoneId> defaultZone );


Expand All @@ -81,9 +89,11 @@ public static void registerTemporalFunctions( Procedures procedures ) throws Pro
private static final List<FieldSignature> INPUT_SIGNATURE = singletonList( inputField( private static final List<FieldSignature> INPUT_SIGNATURE = singletonList( inputField(
"input", Neo4jTypes.NTAny, nullValue( Neo4jTypes.NTAny ) ) ); "input", Neo4jTypes.NTAny, nullValue( Neo4jTypes.NTAny ) ) );
private static final String[] ALLOWED = {}; private static final String[] ALLOWED = {};

private final UserFunctionSignature signature; private final UserFunctionSignature signature;
private final Supplier<ZoneId> defaultZone;


TemporalFunction( Neo4jTypes.AnyType result ) TemporalFunction( Neo4jTypes.AnyType result, Supplier<ZoneId> defaultZone )
{ {
String basename = basename( getClass() ); String basename = basename( getClass() );
assert result.getClass().getSimpleName().equals( basename + "Type" ) : "result type should match function name"; assert result.getClass().getSimpleName().equals( basename + "Type" ) : "result type should match function name";
Expand All @@ -92,6 +102,7 @@ public static void registerTemporalFunctions( Procedures procedures ) throws Pro
new QualifiedName( new String[0], basename.toLowerCase() ), new QualifiedName( new String[0], basename.toLowerCase() ),
INPUT_SIGNATURE, result, null, ALLOWED, INPUT_SIGNATURE, result, null, ALLOWED,
description == null ? null : description.value(), true ); description == null ? null : description.value(), true );
this.defaultZone = defaultZone;
} }


private static void register( TemporalFunction<?> base, Procedures procedures ) throws ProcedureException private static void register( TemporalFunction<?> base, Procedures procedures ) throws ProcedureException
Expand Down Expand Up @@ -147,42 +158,36 @@ public final T 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 now( ctx.get( DEFAULT_CLOCK ), null ); return now( ctx.get( DEFAULT_CLOCK ), null, defaultZone );
} }
else if ( input.length > 1 ) else if ( input.length > 1 )
{ {
return positionalCreate( input ); return positionalCreate( input );
} }
else if ( input[0] instanceof TextValue ) else if ( input[0] instanceof TextValue )
{ {
return parse( (TextValue) input[0], defaultZone( ctx ) ); return parse( (TextValue) input[0], defaultZone );
} }
else if ( input[0] instanceof TemporalValue ) else if ( input[0] instanceof TemporalValue )
{ {
return select( input[0], defaultZone( ctx ) ); return select( input[0], defaultZone );
} }
else if ( input[0] instanceof MapValue ) else if ( input[0] instanceof MapValue )
{ {
MapValue map = (MapValue) input[0]; MapValue map = (MapValue) input[0];
String timezone = onlyTimezone( map ); String timezone = onlyTimezone( map );
if ( timezone != null ) if ( timezone != null )
{ {
return now( ctx.get( DEFAULT_CLOCK ), timezone ); return now( ctx.get( DEFAULT_CLOCK ), timezone, defaultZone );
} }
return build( map, defaultZone( ctx ) ); return build( map, defaultZone );
} }
else else
{ {
throw new ProcedureException( Status.Procedure.ProcedureCallFailed, "Invalid call signature" ); throw new ProcedureException( Status.Procedure.ProcedureCallFailed, "Invalid call signature" );
} }
} }


private static Supplier<ZoneId> defaultZone( Context ctx ) throws ProcedureException
{
Clock clock = ctx.get( DEFAULT_CLOCK );
return clock::getZone;
}

private static String onlyTimezone( MapValue map ) private static String onlyTimezone( MapValue map )
{ {
if ( map.size() == 1 ) if ( map.size() == 1 )
Expand Down Expand Up @@ -256,12 +261,12 @@ public T 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.length == 1) ) ((input[0] == NO_VALUE || input[0] == null) && input.length == 1) )
{ {
return function.now( ctx.get( key ), null ); return function.now( ctx.get( key ), null, function.defaultZone );
} }
else if ( input.length == 1 && input[0] instanceof TextValue ) else if ( input.length == 1 && input[0] instanceof TextValue )
{ {
TextValue timezone = (TextValue) input[0]; TextValue timezone = (TextValue) input[0];
return function.now( ctx.get( key ), timezone.stringValue() ); return function.now( ctx.get( key ), timezone.stringValue(), function.defaultZone );
} }
else else
{ {
Expand Down Expand Up @@ -298,7 +303,7 @@ public T apply( Context ctx, AnyValue[] args ) throws ProcedureException
unit( ((TextValue) unit).stringValue() ), unit( ((TextValue) unit).stringValue() ),
(TemporalValue)input, (TemporalValue)input,
(MapValue) fields, (MapValue) fields,
defaultZone( ctx ) ); function.defaultZone );
} }
} }
throw new ProcedureException( Status.Procedure.ProcedureCallFailed, "Invalid call signature" ); throw new ProcedureException( Status.Procedure.ProcedureCallFailed, "Invalid call signature" );
Expand Down
Expand Up @@ -36,15 +36,15 @@
@Description( "Create a Time instant." ) @Description( "Create a Time instant." )
class TimeFunction extends TemporalFunction<TimeValue> class TimeFunction extends TemporalFunction<TimeValue>
{ {
TimeFunction() TimeFunction( Supplier<ZoneId> defaultZone )
{ {
super( NTTime ); super( NTTime, defaultZone );
} }


@Override @Override
protected TimeValue now( Clock clock, String timezone ) protected TimeValue now( Clock clock, String timezone, Supplier<ZoneId> defaultZone )
{ {
return timezone == null ? TimeValue.now( clock ) : TimeValue.now( clock, timezone ); return timezone == null ? TimeValue.now( clock, defaultZone ) : TimeValue.now( clock, timezone );
} }


@Override @Override
Expand Down

0 comments on commit 1d1ac2c

Please sign in to comment.