Skip to content

Commit

Permalink
Add configuration for TimeZone.
Browse files Browse the repository at this point in the history
[resolves #520]

Signed-off-by: Mark Paluch <mpaluch@vmware.com>
  • Loading branch information
mp911de committed Jun 21, 2022
1 parent 2e0969e commit ba34660
Show file tree
Hide file tree
Showing 19 changed files with 496 additions and 109 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Optional)_
| `targetServerType` | Type of server to use when using multi-host operations. Supported values: `ANY`, `PRIMARY`, `SECONDARY`, `PREFER_SECONDARY`. Defaults to `ANY`. _(Optional)_
| `tcpNoDelay` | Enable/disable TCP NoDelay. Enabled by default. _(Optional)_
| `tcpKeepAlive` | Enable/disable TCP KeepAlive. Disabled by default. _(Optional)_
| `timeZone` | Configure the session timezone to control conversion of local temporal representations. Defaults to `TimeZone.getDefault()` _(Optional)_

**Programmatic Configuration**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
Expand Down Expand Up @@ -120,6 +121,8 @@ public final class PostgresqlConnectionConfiguration {

private final boolean tcpNoDelay;

private final TimeZone timeZone;

private final String username;

private PostgresqlConnectionConfiguration(String applicationName, boolean autodetectExtensions, @Nullable boolean compatibilityMode, @Nullable Duration connectTimeout, @Nullable String database,
Expand All @@ -130,7 +133,7 @@ private PostgresqlConnectionConfiguration(String applicationName, boolean autode
LogLevel noticeLogLevel, @Nullable Map<String, String> options, @Nullable CharSequence password, boolean preferAttachedBuffers,
int preparedStatementCacheQueries, @Nullable String schema,
@Nullable SingleHostConfiguration singleHostConfiguration, SSLConfig sslConfig, @Nullable Duration statementTimeout,
boolean tcpKeepAlive, boolean tcpNoDelay,
boolean tcpKeepAlive, boolean tcpNoDelay, TimeZone timeZone,
String username) {
this.applicationName = Assert.requireNonNull(applicationName, "applicationName must not be null");
this.autodetectExtensions = autodetectExtensions;
Expand Down Expand Up @@ -167,6 +170,7 @@ private PostgresqlConnectionConfiguration(String applicationName, boolean autode
this.sslConfig = sslConfig;
this.tcpKeepAlive = tcpKeepAlive;
this.tcpNoDelay = tcpNoDelay;
this.timeZone = timeZone;
this.username = Assert.requireNonNull(username, "username must not be null");
}

Expand Down Expand Up @@ -202,6 +206,7 @@ public String toString() {
", statementTimeout=" + this.statementTimeout +
", tcpKeepAlive=" + this.tcpKeepAlive +
", tcpNoDelay=" + this.tcpNoDelay +
", timeZone=" + this.timeZone +
", username='" + this.username + '\'' +
'}';
}
Expand Down Expand Up @@ -305,6 +310,10 @@ boolean isTcpNoDelay() {
return this.tcpNoDelay;
}

TimeZone getTimeZone() {
return this.timeZone;
}

SSLConfig getSslConfig() {
return this.sslConfig;
}
Expand Down Expand Up @@ -408,6 +417,8 @@ public static final class Builder {

private boolean tcpNoDelay = true;

private TimeZone timeZone = TimeZone.getDefault();

@Nullable
private LoopResources loopResources = null;

Expand Down Expand Up @@ -467,7 +478,7 @@ public PostgresqlConnectionConfiguration build() {
this.extensions, this.fetchSize, this.forceBinary, this.lockWaitTimeout, this.loopResources, multiHostConfiguration,
this.noticeLogLevel, this.options, this.password, this.preferAttachedBuffers,
this.preparedStatementCacheQueries, this.schema, singleHostConfiguration,
this.createSslConfig(), this.statementTimeout, this.tcpKeepAlive, this.tcpNoDelay, this.username);
this.createSslConfig(), this.statementTimeout, this.tcpKeepAlive, this.tcpNoDelay, this.timeZone, this.username);
}

/**
Expand Down Expand Up @@ -977,6 +988,33 @@ public Builder tcpNoDelay(boolean enabled) {
return this;
}

/**
* Configure the session timezone.
*
* @param timeZone the timeZone identifier
* @return this {@link Builder}
* @throws IllegalArgumentException if {@code timeZone} is empty or {@code null}
* @see TimeZone#getTimeZone(String)
* @since 1.0
*/
public Builder timeZone(String timeZone) {
return timeZone(TimeZone.getTimeZone(Assert.requireNotEmpty(timeZone, "timeZone must not be empty")));
}

/**
* Configure the session timezone.
*
* @param timeZone the timeZone identifier
* @return this {@link Builder}
* @throws IllegalArgumentException if {@code timeZone} is {@code null}
* @see TimeZone#getTimeZone(String)
* @since 1.0
*/
public Builder timeZone(TimeZone timeZone) {
this.timeZone = Assert.requireNonNull(timeZone, "timeZone must not be null");
return this;
}

/**
* Configure the username.
*
Expand Down Expand Up @@ -1019,6 +1057,7 @@ public String toString() {
", sslHostnameVerifier='" + this.sslHostnameVerifier + '\'' +
", tcpKeepAlive='" + this.tcpKeepAlive + '\'' +
", tcpNoDelay='" + this.tcpNoDelay + '\'' +
", timeZone='" + this.timeZone + '\'' +
", username='" + this.username + '\'' +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@

import javax.net.ssl.HostnameVerifier;
import java.time.Duration;
import java.time.ZoneId;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.function.Function;

import static io.r2dbc.spi.ConnectionFactoryOptions.CONNECT_TIMEOUT;
Expand Down Expand Up @@ -245,6 +247,13 @@ public final class PostgresqlConnectionFactoryProvider implements ConnectionFact
*/
public static final Option<Boolean> TCP_NODELAY = Option.valueOf("tcpNoDelay");

/**
* Configure the session time zone.
*
* @since 1.0
*/
public static final Option<TimeZone> TIME_ZONE = Option.valueOf("timeZone");

/**
* Returns a new {@link PostgresqlConnectionConfiguration.Builder} configured with the given {@link ConnectionFactoryOptions}.
*
Expand Down Expand Up @@ -342,6 +351,18 @@ private static PostgresqlConnectionConfiguration.Builder fromConnectionFactoryOp
mapper.from(STATEMENT_TIMEOUT).map(OptionMapper::toDuration).to(builder::statementTimeout);
mapper.from(TCP_KEEPALIVE).map(OptionMapper::toBoolean).to(builder::tcpKeepAlive);
mapper.from(TCP_NODELAY).map(OptionMapper::toBoolean).to(builder::tcpNoDelay);
mapper.from(TIME_ZONE).map(it -> {

if (it instanceof TimeZone) {
return (TimeZone) it;
}

if (it instanceof ZoneId) {
return TimeZone.getTimeZone((ZoneId) it);
}

return TimeZone.getTimeZone(it.toString());
}).to(builder::timeZone);
builder.username("" + options.getRequiredValue(USER));

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.r2dbc.postgresql.authentication.SASLAuthenticationHandler;
import io.r2dbc.postgresql.client.Client;
import io.r2dbc.postgresql.client.ConnectionSettings;
import io.r2dbc.postgresql.client.PostgresStartupParameterProvider;
import io.r2dbc.postgresql.client.StartupMessageFlow;
import io.r2dbc.postgresql.message.backend.AuthenticationMessage;
import io.r2dbc.postgresql.util.Assert;
Expand All @@ -44,10 +45,15 @@ public Mono<Client> connect(SocketAddress endpoint, ConnectionSettings settings)

return this.upstreamFunction.connect(endpoint, settings)
.delayUntil(client -> StartupMessageFlow
.exchange(this.configuration.getApplicationName(), this::getAuthenticationHandler, client, this.configuration.getDatabase(), this.configuration.getUsername(), settings)
.exchange(this::getAuthenticationHandler, client, this.configuration.getDatabase(), this.configuration.getUsername(),
getParameterProvider(this.configuration, settings))
.handle(ExceptionFactory.INSTANCE::handleErrorResponse));
}

private static PostgresStartupParameterProvider getParameterProvider(PostgresqlConnectionConfiguration configuration, ConnectionSettings settings) {
return new PostgresStartupParameterProvider(configuration.getApplicationName(), configuration.getTimeZone(), settings);
}

protected AuthenticationHandler getAuthenticationHandler(AuthenticationMessage message) {
if (PasswordAuthenticationHandler.supports(message)) {
CharSequence password = Assert.requireNonNull(this.configuration.getPassword(), "Password must not be null");
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/io/r2dbc/postgresql/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.TimeZone;
import java.util.function.Consumer;
import java.util.function.Predicate;

Expand Down Expand Up @@ -127,6 +128,14 @@ default Flux<BackendMessage> exchange(Publisher<FrontendMessage> requests) {
*/
Optional<Integer> getSecretKey();

/**
* Returns the current time zone.
*
* @return the current time zone
* @since 1.0
*/
Optional<TimeZone> getTimeZone();

/**
* Returns the current transaction status.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.r2dbc.postgresql.client;

import io.r2dbc.postgresql.message.frontend.StartupMessage;
import io.r2dbc.postgresql.util.Assert;
import reactor.util.annotation.Nullable;

import java.util.Map;
import java.util.TimeZone;

/**
* {@link StartupMessage.StartupParameterProvider} for generic Postgres options.
*
* @since 1.0
*/
public final class PostgresStartupParameterProvider implements StartupMessage.StartupParameterProvider {

private final String applicationName;

private final TimeZone timeZone;

@Nullable
private final Map<String, String> options;

public PostgresStartupParameterProvider(String applicationName, TimeZone timeZone, @Nullable Map<String, String> options) {
this.applicationName = Assert.requireNonNull(applicationName, "applicationName must not be null");
this.timeZone = Assert.requireNonNull(timeZone, "timeZone must not be null");
this.options = options;
}

public PostgresStartupParameterProvider(String applicationName, TimeZone timeZone, ConnectionSettings settings) {
this.applicationName = Assert.requireNonNull(applicationName, "applicationName must not be null");
this.timeZone = Assert.requireNonNull(timeZone, "timeZone must not be null");
this.options = settings.getStartupOptions();
}

@Override
public void accept(StartupMessage.ParameterWriter writer) {

writer.write("application_name", this.applicationName);
writer.write("client_encoding", "utf8");
writer.write("DateStyle", "ISO");
writer.write("extra_float_digits", "2");
writer.write("TimeZone", TimeZoneUtils.createPostgresTimeZone(this.timeZone));

if (this.options != null) {
for (Map.Entry<String, String> option : this.options.entrySet()) {
writer.write(option.getKey(), option.getValue());
}
}

}

}
38 changes: 27 additions & 11 deletions src/main/java/io/r2dbc/postgresql/client/ReactorNettyClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import java.util.Optional;
import java.util.Queue;
import java.util.StringJoiner;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
Expand Down Expand Up @@ -116,6 +117,8 @@ public final class ReactorNettyClient implements Client {

private volatile Integer secretKey;

private volatile TimeZone timeZone;

private volatile TransactionStatus transactionStatus = IDLE;

private volatile Version version = new Version("", 0);
Expand Down Expand Up @@ -298,24 +301,32 @@ private boolean consumeMessage(BackendMessage message) {

private void handleParameterStatus(ParameterStatus message) {

Version existingVersion = this.version;
String name = message.getName();

String versionString = existingVersion.getVersion();
int versionNum = existingVersion.getVersionNumber();
if (name.equals("server_version_num") || name.equals("server_version")) {
Version existingVersion = this.version;

if (message.getName().equals("server_version_num")) {
versionNum = Integer.parseInt(message.getValue());
}
String versionString = existingVersion.getVersion();
int versionNum = existingVersion.getVersionNumber();

if (message.getName().equals("server_version")) {
versionString = message.getValue();
if (name.equals("server_version_num")) {
versionNum = Integer.parseInt(message.getValue());
}

if (versionNum == 0) {
versionNum = Version.parseServerVersionStr(versionString);
if (name.equals("server_version")) {
versionString = message.getValue();

if (versionNum == 0) {
versionNum = Version.parseServerVersionStr(versionString);
}
}

this.version = new Version(versionString, versionNum);
}

this.version = new Version(versionString, versionNum);
if (name.equals("TimeZone")) {
this.timeZone = TimeZoneUtils.parseBackendTimeZone(message.getValue());
}
}

/**
Expand Down Expand Up @@ -446,6 +457,11 @@ public Optional<Integer> getSecretKey() {
return Optional.ofNullable(this.secretKey);
}

@Override
public Optional<TimeZone> getTimeZone() {
return Optional.ofNullable(this.timeZone);
}

@Override
public TransactionStatus getTransactionStatus() {
return this.transactionStatus;
Expand Down
Loading

0 comments on commit ba34660

Please sign in to comment.