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 ) );

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

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

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

import org.neo4j.configuration.Description;
Expand All @@ -45,6 +47,7 @@
import org.neo4j.kernel.configuration.ssl.SslPolicyConfigValidator;
import org.neo4j.logging.Level;
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.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",
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 =
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 =
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" )
@Internal
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 );
EmbeddedProxySPI proxySPI = platform.dependencies.resolveDependency( EmbeddedProxySPI.class );

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

Expand Down Expand Up @@ -300,7 +301,7 @@ private Procedures setupProcedures( PlatformModule platform, EditionModule editi
// Edition procedures
try
{
editionModule.registerProcedures( procedures );
editionModule.registerProcedures( procedures, procedureConfig );
}
catch ( KernelException e )
{
Expand Down
Expand Up @@ -43,6 +43,7 @@
import org.neo4j.kernel.impl.locking.Locks;
import org.neo4j.kernel.impl.locking.StatementLocksFactory;
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.storageengine.impl.recordstorage.id.BufferedIdController;
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(
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.TokenProcedures.class );
procedures.registerProcedure( org.neo4j.kernel.builtinprocs.BuiltInDbmsProcedures.class );
procedures.registerBuiltInFunctions( org.neo4j.kernel.builtinprocs.BuiltInFunctions.class );
registerTemporalFunctions( procedures );
registerTemporalFunctions( procedures, procedureConfig );

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

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

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

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

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

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

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();

public ZoneId getDefaultTemporalTimeZone()
{
return defaultTemporalTimeZone;
}

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

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

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

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

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

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 );
register( new LocalDateTimeFunction(), procedures );
register( new DateFunction(), procedures );
register( new TimeFunction(), procedures );
register( new LocalTimeFunction(), procedures );
Supplier<ZoneId> defaultZone = procedureConfig::getDefaultTemporalTimeZone;
register( new DateTimeFunction( defaultZone ), procedures );
register( new LocalDateTimeFunction( defaultZone ), procedures );
register( new DateFunction( defaultZone ), procedures );
register( new TimeFunction( defaultZone ), procedures );
register( new LocalTimeFunction( defaultZone ), procedures );
DurationFunction.register( procedures );
}

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 );

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

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

TemporalFunction( Neo4jTypes.AnyType result )
TemporalFunction( Neo4jTypes.AnyType result, Supplier<ZoneId> defaultZone )
{
String basename = basename( getClass() );
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() ),
INPUT_SIGNATURE, result, null, ALLOWED,
description == null ? null : description.value(), true );
this.defaultZone = defaultZone;
}

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

@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
Expand Down

0 comments on commit 1d1ac2c

Please sign in to comment.