Skip to content

Commit

Permalink
Add support for timezone parameter to JDBC driver
Browse files Browse the repository at this point in the history
  • Loading branch information
hashhar committed Oct 16, 2023
1 parent 91ee252 commit 8db9844
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ public ClientSession toClientSession(TrinoUri uri)
.clientInfo(clientInfo.orElse(null))
.catalog(uri.getCatalog().orElse(catalog.orElse(null)))
.schema(uri.getSchema().orElse(schema.orElse(null)))
.timeZone(timeZone)
.timeZone(uri.getTimeZone())
.locale(Locale.getDefault())
.resourceEstimates(toResourceEstimates(resourceEstimates))
.properties(toProperties(sessionProperties))
Expand Down Expand Up @@ -409,6 +409,7 @@ public TrinoUri getTrinoUri(Map<PropertyName, String> restrictedProperties)
traceToken.ifPresent(builder::setTraceToken);
socksProxy.ifPresent(builder::setSocksProxy);
httpProxy.ifPresent(builder::setHttpProxy);
builder.setTimeZone(timeZone);
builder.setDisableCompression(disableCompression);
TrinoUri trinoUri;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.ietf.jgss.GSSCredential;

import java.io.File;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -97,6 +98,7 @@ enum SslVerificationMode
public static final ConnectionProperty<String, Class<? extends DnsResolver>> DNS_RESOLVER = new Resolver();
public static final ConnectionProperty<String, String> DNS_RESOLVER_CONTEXT = new ResolverContext();
public static final ConnectionProperty<String, String> HOSTNAME_IN_CERTIFICATE = new HostnameInCertificate();
public static final ConnectionProperty<String, ZoneId> TIMEZONE = new TimeZone();

private static final Set<ConnectionProperty<?, ?>> ALL_PROPERTIES = ImmutableSet.<ConnectionProperty<?, ?>>builder()
.add(USER)
Expand Down Expand Up @@ -141,6 +143,7 @@ enum SslVerificationMode
.add(DNS_RESOLVER)
.add(DNS_RESOLVER_CONTEXT)
.add(HOSTNAME_IN_CERTIFICATE)
.add(TIMEZONE)
.build();

private static final Map<String, ConnectionProperty<?, ?>> KEY_LOOKUP = unmodifiableMap(ALL_PROPERTIES.stream()
Expand Down Expand Up @@ -704,6 +707,15 @@ public HostnameInCertificate()
}
}

private static class TimeZone
extends AbstractConnectionProperty<String, ZoneId>
{
public TimeZone()
{
super(PropertyName.TIMEZONE, NOT_REQUIRED, ALLOWED, ZoneId::of);
}
}

private static class MapPropertyParser
{
private static final CharMatcher PRINTABLE_ASCII = CharMatcher.inRange((char) 0x21, (char) 0x7E);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public enum PropertyName
DNS_RESOLVER("dnsResolver"),
DNS_RESOLVER_CONTEXT("dnsResolverContext"),
HOSTNAME_IN_CERTIFICATE("hostnameInCertificate"),
TIMEZONE("timezone"),
// these two are not actual properties but parts of the path
CATALOG("catalog"),
SCHEMA("schema");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.time.Duration;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -104,6 +105,7 @@
import static io.trino.client.uri.ConnectionProperties.SslVerificationMode.CA;
import static io.trino.client.uri.ConnectionProperties.SslVerificationMode.FULL;
import static io.trino.client.uri.ConnectionProperties.SslVerificationMode.NONE;
import static io.trino.client.uri.ConnectionProperties.TIMEZONE;
import static io.trino.client.uri.ConnectionProperties.TRACE_TOKEN;
import static io.trino.client.uri.ConnectionProperties.USER;
import static java.lang.String.format;
Expand Down Expand Up @@ -159,6 +161,7 @@ public class TrinoUri
private Optional<KnownTokenCache> externalAuthenticationTokenCache;
private Optional<Map<String, String>> extraCredentials;
private Optional<String> hostnameInCertificate;
private Optional<ZoneId> timeZone;
private Optional<String> clientInfo;
private Optional<String> clientTags;
private Optional<String> traceToken;
Expand Down Expand Up @@ -211,6 +214,7 @@ private TrinoUri(
Optional<KnownTokenCache> externalAuthenticationTokenCache,
Optional<Map<String, String>> extraCredentials,
Optional<String> hostnameInCertificate,
Optional<ZoneId> timeZone,
Optional<String> clientInfo,
Optional<String> clientTags,
Optional<String> traceToken,
Expand Down Expand Up @@ -262,6 +266,7 @@ private TrinoUri(
this.externalAuthenticationTokenCache = EXTERNAL_AUTHENTICATION_TOKEN_CACHE.getValueOrDefault(urlProperties, externalAuthenticationTokenCache);
this.extraCredentials = EXTRA_CREDENTIALS.getValueOrDefault(urlProperties, extraCredentials);
this.hostnameInCertificate = HOSTNAME_IN_CERTIFICATE.getValueOrDefault(urlProperties, hostnameInCertificate);
this.timeZone = TIMEZONE.getValueOrDefault(urlProperties, timeZone);
this.clientInfo = CLIENT_INFO.getValueOrDefault(urlProperties, clientInfo);
this.clientTags = CLIENT_TAGS.getValueOrDefault(urlProperties, clientTags);
this.traceToken = TRACE_TOKEN.getValueOrDefault(urlProperties, traceToken);
Expand Down Expand Up @@ -347,6 +352,7 @@ private Properties buildProperties()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining(";"))));
hostnameInCertificate.ifPresent(value -> properties.setProperty(PropertyName.HOSTNAME_IN_CERTIFICATE.toString(), value));
timeZone.ifPresent(value -> properties.setProperty(PropertyName.TIMEZONE.toString(), value.getId()));
clientInfo.ifPresent(value -> properties.setProperty(PropertyName.CLIENT_INFO.toString(), value));
clientTags.ifPresent(value -> properties.setProperty(PropertyName.CLIENT_TAGS.toString(), value));
traceToken.ifPresent(value -> properties.setProperty(PropertyName.TRACE_TOKEN.toString(), value));
Expand Down Expand Up @@ -404,6 +410,7 @@ protected TrinoUri(URI uri, Properties driverProperties)
this.externalAuthenticationTokenCache = EXTERNAL_AUTHENTICATION_TOKEN_CACHE.getValue(properties);
this.extraCredentials = EXTRA_CREDENTIALS.getValue(properties);
this.hostnameInCertificate = HOSTNAME_IN_CERTIFICATE.getValue(properties);
this.timeZone = TIMEZONE.getValue(properties);
this.clientInfo = CLIENT_INFO.getValue(properties);
this.clientTags = CLIENT_TAGS.getValue(properties);
this.traceToken = TRACE_TOKEN.getValue(properties);
Expand Down Expand Up @@ -531,6 +538,11 @@ public boolean isAssumeLiteralUnderscoreInMetadataCallsForNonConformingClients()
return assumeLiteralUnderscoreInMetadataCallsForNonConformingClients.orElse(false);
}

public ZoneId getTimeZone()
{
return timeZone.orElseGet(ZoneId::systemDefault);
}

public Properties getProperties()
{
return properties;
Expand Down Expand Up @@ -909,6 +921,7 @@ public static final class Builder
private KnownTokenCache externalAuthenticationTokenCache;
private Map<String, String> extraCredentials;
private String hostnameInCertificate;
private ZoneId timeZone;
private String clientInfo;
private String clientTags;
private String traceToken;
Expand Down Expand Up @@ -1166,6 +1179,12 @@ public Builder setHostnameInCertificate(String hostnameInCertificate)
return this;
}

public Builder setTimeZone(ZoneId timeZone)
{
this.timeZone = requireNonNull(timeZone, "timeZone is null");
return this;
}

public Builder setClientInfo(String clientInfo)
{
this.clientInfo = requireNonNull(clientInfo, "clientInfo is null");
Expand Down Expand Up @@ -1239,6 +1258,7 @@ public TrinoUri build()
Optional.ofNullable(externalAuthenticationTokenCache),
Optional.ofNullable(extraCredentials),
Optional.ofNullable(hostnameInCertificate),
Optional.ofNullable(timeZone),
Optional.ofNullable(clientInfo),
Optional.ofNullable(clientTags),
Optional.ofNullable(traceToken),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public class TrinoConnection
uri.getTraceToken().ifPresent(tags -> clientInfo.put(TRACE_TOKEN, tags));

roles.putAll(uri.getRoles());
timeZoneId.set(ZoneId.systemDefault());
timeZoneId.set(uri.getTimeZone());
locale.set(Locale.getDefault());
sessionProperties.putAll(uri.getSessionProperties());
}
Expand Down
34 changes: 34 additions & 0 deletions client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.zone.ZoneRulesException;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
Expand Down Expand Up @@ -666,6 +667,32 @@ public void testSetTimeZoneId()
assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis()));
}
}

try (Connection connection = createConnectionWithParameter("timezone=Asia/Kolkata")) {
try (Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql)) {
assertTrue(rs.next());
assertEquals(rs.getString("zone"), "Asia/Kolkata");
// setting the session timezone has no effect on the interpretation of timestamps in the JDBC driver
assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis()));
}
}

try (Connection connection = createConnectionWithParameter("timezone=UTC+05:30")) {
try (Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql)) {
assertTrue(rs.next());
assertEquals(rs.getString("zone"), "+05:30");
// setting the session timezone has no effect on the interpretation of timestamps in the JDBC driver
assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis()));
}
}

assertThatThrownBy(() -> createConnectionWithParameter("timezone=Asia/NOT_FOUND"))
.isInstanceOf(SQLException.class)
.hasMessage("Connection property timezone value is invalid: Asia/NOT_FOUND")
.hasRootCauseInstanceOf(ZoneRulesException.class)
.hasRootCauseMessage("Unknown time-zone ID: Asia/NOT_FOUND");
}

@Test
Expand Down Expand Up @@ -1200,6 +1227,13 @@ private Connection createConnection(String catalog, String schema)
return DriverManager.getConnection(url, "test", null);
}

private Connection createConnectionWithParameter(String parameter)
throws SQLException
{
String url = format("jdbc:trino://%s?%s", server.getAddress(), parameter);
return DriverManager.getConnection(url, "test", null);
}

private static Properties toProperties(Map<String, String> map)
{
Properties properties = new Properties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.net.URI;
import java.sql.SQLException;
import java.time.ZoneId;
import java.time.zone.ZoneRulesException;
import java.util.Properties;

import static io.trino.client.uri.PropertyName.CLIENT_TAGS;
Expand Down Expand Up @@ -423,6 +425,26 @@ public void testAssumeLiteralUnderscoreInMetadataCallsForNonConformingClients()
assertThat(parameters.isAssumeLiteralNamesInMetadataCallsForNonConformingClients()).isFalse();
}

@Test
public void testTimezone()
throws SQLException
{
TrinoDriverUri defaultParameters = createDriverUri("jdbc:trino://localhost:8080");
assertThat(defaultParameters.getTimeZone()).isEqualTo(ZoneId.systemDefault());

TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?timezone=Asia/Kolkata");
assertThat(parameters.getTimeZone()).isEqualTo(ZoneId.of("Asia/Kolkata"));

TrinoDriverUri offsetParameters = createDriverUri("jdbc:trino://localhost:8080?timezone=UTC+05:30");
assertThat(offsetParameters.getTimeZone()).isEqualTo(ZoneId.of("UTC+05:30"));

assertThatThrownBy(() -> createDriverUri("jdbc:trino://localhost:8080?timezone=Asia/NOT_FOUND"))
.isInstanceOf(SQLException.class)
.hasMessage("Connection property timezone value is invalid: Asia/NOT_FOUND")
.hasRootCauseInstanceOf(ZoneRulesException.class)
.hasRootCauseMessage("Unknown time-zone ID: Asia/NOT_FOUND");
}

private static void assertUriPortScheme(TrinoDriverUri parameters, int port, String scheme)
{
URI uri = parameters.getHttpUri();
Expand Down
4 changes: 4 additions & 0 deletions docs/src/main/sphinx/client/jdbc.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,8 @@ may not be specified using both methods.
treated as underscores. You can use this as a workaround for
applications that do not escape schema or table names when passing them
to ``DatabaseMetaData`` methods as schema or table name patterns.
* - ``timezone``
- Sets the time zone for the session using the `time zone passed
<https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/ZoneId.html#of(java.lang.String)>`_. Defaults
to the timezone of the JVM running the JDBC driver.
```

0 comments on commit 8db9844

Please sign in to comment.