Skip to content

Commit 059fc07

Browse files
authored
Merge pull request #1738 from ClickHouse/fix_timezone_in_response_header
Copy timezone from response if differs from config server_time_zone
2 parents 5d9f69d + 19fd2f2 commit 059fc07

File tree

7 files changed

+163
-28
lines changed

7 files changed

+163
-28
lines changed

clickhouse-cli-client/src/test/java/com/clickhouse/client/cli/ClickHouseCommandLineClientTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.clickhouse.client.ClientIntegrationTest;
1111
import com.clickhouse.client.cli.config.ClickHouseCommandLineOption;
1212
import com.clickhouse.data.ClickHouseCompression;
13+
import com.clickhouse.data.ClickHouseFormat;
1314
import org.testcontainers.containers.GenericContainer;
1415
import org.testng.SkipException;
1516
import org.testng.annotations.BeforeClass;
@@ -199,4 +200,9 @@ public void testLoadBalancingPolicyFailover(ClickHouseLoadBalancingPolicy loadBa
199200
public void testFailover() {
200201
throw new SkipException("Skip due to failover is not supported");
201202
}
203+
204+
@Override
205+
public void testServerTimezoneAppliedFromHeader(ClickHouseFormat format) throws Exception {
206+
throw new SkipException("Skip due to session timezone is not supported");
207+
}
202208
}

clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseStreamResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import java.io.IOException;
44
import java.io.Serializable;
55
import java.util.Collections;
6+
import java.util.HashMap;
67
import java.util.List;
78
import java.util.Map;
89
import java.util.TimeZone;
910

11+
import com.clickhouse.client.config.ClickHouseClientOption;
12+
import com.clickhouse.config.ClickHouseOption;
1013
import com.clickhouse.data.ClickHouseColumn;
1114
import com.clickhouse.data.ClickHouseDataProcessor;
1215
import com.clickhouse.data.ClickHouseDataStreamFactory;
@@ -69,6 +72,11 @@ protected ClickHouseStreamResponse(ClickHouseConfig config, ClickHouseInputStrea
6972
this.timeZone = timeZone;
7073
boolean hasError = true;
7174
try {
75+
if (timeZone != null && config.isUseServerTimeZone() && !config.getUseTimeZone().equals(timeZone)) {
76+
Map<ClickHouseOption, Serializable> configOptions = new HashMap<>(config.getAllOptions());
77+
configOptions.put(ClickHouseClientOption.SERVER_TIME_ZONE, timeZone.getID());
78+
config = new ClickHouseConfig(configOptions);
79+
}
7280
this.processor = ClickHouseDataStreamFactory.getInstance().getProcessor(config, input, null, settings,
7381
columns);
7482
hasError = false;

clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@
7070
import java.nio.file.Files;
7171
import java.nio.file.Path;
7272
import java.nio.file.Paths;
73+
import java.time.Duration;
74+
import java.time.Instant;
75+
import java.time.LocalDateTime;
76+
import java.time.ZoneId;
77+
import java.time.ZoneOffset;
78+
import java.time.ZonedDateTime;
79+
import java.time.format.DateTimeFormatter;
7380
import java.util.ArrayList;
7481
import java.util.Collections;
7582
import java.util.HashMap;
@@ -2695,4 +2702,42 @@ public void testFailover() throws ClickHouseException {
26952702
Assert.assertEquals(response.firstRecord().getValue(0).asInteger(), 1);
26962703
}
26972704
}
2705+
2706+
@Test(groups = {"integration"}, dataProvider = "testServerTimezoneAppliedFromHeaderProvider")
2707+
public void testServerTimezoneAppliedFromHeader(ClickHouseFormat format) throws Exception {
2708+
System.out.println("Testing with " + format + " format");
2709+
ClickHouseNode server = getServer();
2710+
2711+
ZoneId custZoneId = ZoneId.of("America/Los_Angeles");
2712+
2713+
final String sql = "SELECT now(), toDateTime(now(), 'UTC') as utc_time, serverTimezone() SETTINGS session_timezone = 'America/Los_Angeles'";
2714+
try (ClickHouseClient client = getClient();
2715+
ClickHouseResponse response = newRequest(client, server)
2716+
.query(sql, UUID.randomUUID().toString())
2717+
.option(ClickHouseClientOption.FORMAT, format)
2718+
.executeAndWait()) {
2719+
2720+
Assert.assertEquals(response.getTimeZone().getID(), "America/Los_Angeles", "Timezone should be applied from the query settings");
2721+
2722+
ClickHouseRecord record = response.firstRecord();
2723+
final ZonedDateTime now = record.getValue(0).asZonedDateTime();
2724+
final ZonedDateTime utcTime = record.getValue(1).asZonedDateTime();
2725+
final String serverTimezone = record.getValue(2).asString();
2726+
Assert.assertNotEquals(serverTimezone, "America/Los_Angeles", "Server timezone should be applied from the query settings");
2727+
2728+
2729+
System.out.println("Now in America/Los_Angeles: " + now);
2730+
System.out.println("UTC Time: " + utcTime);
2731+
System.out.println("UTC Time: " + utcTime.withZoneSameInstant(custZoneId));
2732+
Assert.assertEquals(now, utcTime.withZoneSameInstant(custZoneId));
2733+
}
2734+
}
2735+
2736+
@DataProvider(name = "testServerTimezoneAppliedFromHeaderProvider")
2737+
public static ClickHouseFormat[] testServerTimezoneAppliedFromHeaderProvider() {
2738+
return new ClickHouseFormat[]{
2739+
ClickHouseFormat.TabSeparatedWithNamesAndTypes,
2740+
ClickHouseFormat.RowBinaryWithNamesAndTypes
2741+
};
2742+
}
26982743
}

clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseGrpcResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ static void checkError(Result result) {
2929

3030
protected ClickHouseGrpcResponse(ClickHouseConfig config, Map<String, Serializable> settings,
3131
ClickHouseStreamObserver observer) throws IOException {
32-
super(config, observer.getInputStream(), settings, null, observer.getSummary(), null);
32+
super(config, observer.getInputStream(), settings, null, observer.getSummary(), observer.getTimeZone());
3333

3434
this.observer = observer;
3535
}

clickhouse-grpc-client/src/main/java/com/clickhouse/client/grpc/ClickHouseStreamObserver.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.io.UncheckedIOException;
5+
import java.util.TimeZone;
56
import java.util.concurrent.TimeUnit;
67
import java.util.concurrent.atomic.AtomicBoolean;
78
import java.util.concurrent.atomic.AtomicReference;
@@ -44,6 +45,8 @@ public class ClickHouseStreamObserver implements StreamObserver<Result> {
4445

4546
private final AtomicReference<IOException> errorRef;
4647

48+
private TimeZone timeZone;
49+
4750
protected ClickHouseStreamObserver(ClickHouseConfig config, ClickHouseNode server, ClickHouseOutputStream output) {
4851
this.server = server;
4952

@@ -128,6 +131,11 @@ protected boolean updateStatus(Result result) {
128131
}
129132
}
130133

134+
String tz = result.getTimeZone();
135+
if (!tz.isEmpty()) {
136+
timeZone = TimeZone.getTimeZone(result.getTimeZone());
137+
}
138+
131139
return proceed;
132140
}
133141

@@ -221,4 +229,8 @@ public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedEx
221229
public ClickHouseInputStream getInputStream() {
222230
return this.input;
223231
}
232+
233+
public TimeZone getTimeZone() {
234+
return timeZone;
235+
}
224236
}

clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseResultSetTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
import java.sql.ResultSet;
66
import java.sql.SQLException;
77
import java.sql.Statement;
8+
import java.sql.Time;
9+
import java.sql.Timestamp;
810
import java.time.LocalDate;
911
import java.time.LocalDateTime;
1012
import java.time.OffsetDateTime;
13+
import java.time.ZonedDateTime;
1114
import java.util.Arrays;
15+
import java.util.Calendar;
1216
import java.util.Collections;
1317
import java.util.List;
1418
import java.util.Map;
@@ -18,6 +22,7 @@
1822

1923
import com.clickhouse.client.ClickHouseConfig;
2024
import com.clickhouse.client.ClickHouseSimpleResponse;
25+
import com.clickhouse.client.config.ClickHouseClientOption;
2126
import com.clickhouse.data.ClickHouseColumn;
2227
import com.clickhouse.data.ClickHouseDataType;
2328
import com.clickhouse.data.ClickHouseRecord;
@@ -424,4 +429,32 @@ public void testFetchSize() throws SQLException {
424429
}
425430
}
426431
}
432+
433+
434+
@Test(groups = "integration")
435+
public void testDateTimeWithoutTimezone() throws SQLException {
436+
final String sql = "select now(), toDateTime(now(), 'America/Los_Angeles') as tzTime SETTINGS session_timezone = 'America/Los_Angeles'";
437+
// Default behavior
438+
try (ClickHouseConnection conn = newConnection(new Properties());
439+
Statement stmt = conn.createStatement()) {
440+
ResultSet rs = stmt.executeQuery(sql);
441+
Assert.assertTrue(rs.next());
442+
OffsetDateTime serverNowOffseted = rs.getObject(1, OffsetDateTime.class);
443+
LocalDateTime serverNow = (LocalDateTime) rs.getObject(1);
444+
OffsetDateTime tzTime = (OffsetDateTime) rs.getObject(2);
445+
ZonedDateTime serverNowZoned = rs.getObject(1, ZonedDateTime.class);
446+
Assert.assertTrue(serverNow.isEqual(tzTime.toLocalDateTime()));
447+
Assert.assertTrue(serverNow.isEqual(serverNowOffseted.toLocalDateTime()));
448+
Assert.assertEquals(tzTime.getOffset(), TimeZone.getTimeZone("America/Los_Angeles").toZoneId().getRules().getOffset(tzTime.toInstant()));
449+
Assert.assertEquals(serverNowZoned.getZone(), TimeZone.getTimeZone("America/Los_Angeles").toZoneId());
450+
Assert.assertEquals(serverNowZoned.toLocalDateTime(), serverNow);
451+
452+
Time serverNowTime = rs.getTime(1);
453+
Time tzTimeTime = rs.getTime(2);
454+
Timestamp serverNowTimestamp = rs.getTimestamp(1);
455+
Timestamp tzTimeTimestamp = rs.getTimestamp(2);
456+
Assert.assertEquals(serverNowTime, tzTimeTime);
457+
Assert.assertEquals(serverNowTimestamp, tzTimeTimestamp);
458+
}
459+
}
427460
}

clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseStatementTest.java

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
package com.clickhouse.jdbc;
22

3+
import com.clickhouse.client.ClickHouseClient;
4+
import com.clickhouse.client.ClickHouseParameterizedQuery;
5+
import com.clickhouse.client.ClickHouseProtocol;
6+
import com.clickhouse.client.ClickHouseRequest;
7+
import com.clickhouse.client.config.ClickHouseClientOption;
8+
import com.clickhouse.client.http.config.ClickHouseHttpOption;
9+
import com.clickhouse.data.ClickHouseDataType;
10+
import com.clickhouse.data.ClickHouseValues;
11+
import com.clickhouse.data.value.ClickHouseBitmap;
12+
import com.clickhouse.data.value.ClickHouseDateTimeValue;
13+
import com.clickhouse.data.value.UnsignedByte;
14+
import com.clickhouse.data.value.UnsignedInteger;
15+
import com.clickhouse.data.value.UnsignedLong;
16+
import com.clickhouse.data.value.UnsignedShort;
17+
import org.roaringbitmap.longlong.Roaring64NavigableMap;
18+
import org.testng.Assert;
19+
import org.testng.SkipException;
20+
import org.testng.annotations.DataProvider;
21+
import org.testng.annotations.Test;
22+
323
import java.io.File;
424
import java.io.IOException;
5-
import java.lang.ref.WeakReference;
625
import java.math.BigDecimal;
726
import java.sql.Array;
827
import java.sql.BatchUpdateException;
@@ -14,14 +33,15 @@
1433
import java.sql.Struct;
1534
import java.sql.Time;
1635
import java.sql.Timestamp;
36+
import java.time.Duration;
1737
import java.time.Instant;
1838
import java.time.LocalDate;
1939
import java.time.LocalDateTime;
2040
import java.time.LocalTime;
2141
import java.time.OffsetDateTime;
2242
import java.time.ZoneId;
43+
import java.time.ZoneOffset;
2344
import java.time.ZonedDateTime;
24-
import java.util.ArrayList;
2545
import java.util.Arrays;
2646
import java.util.Calendar;
2747
import java.util.Collections;
@@ -35,33 +55,8 @@
3555
import java.util.concurrent.Executors;
3656
import java.util.concurrent.ScheduledExecutorService;
3757
import java.util.concurrent.TimeUnit;
38-
import java.util.concurrent.atomic.AtomicBoolean;
3958
import java.util.concurrent.atomic.AtomicReference;
4059

41-
import com.clickhouse.client.ClickHouseClient;
42-
import com.clickhouse.client.ClickHouseException;
43-
import com.clickhouse.client.ClickHouseParameterizedQuery;
44-
import com.clickhouse.client.ClickHouseProtocol;
45-
import com.clickhouse.client.ClickHouseRequest;
46-
import com.clickhouse.client.ClickHouseServerForTest;
47-
import com.clickhouse.client.config.ClickHouseClientOption;
48-
import com.clickhouse.client.http.config.ClickHouseHttpOption;
49-
import com.clickhouse.data.ClickHouseDataType;
50-
import com.clickhouse.data.ClickHouseValues;
51-
import com.clickhouse.data.value.ClickHouseBitmap;
52-
import com.clickhouse.data.value.ClickHouseDateTimeValue;
53-
import com.clickhouse.data.value.UnsignedByte;
54-
import com.clickhouse.data.value.UnsignedInteger;
55-
import com.clickhouse.data.value.UnsignedLong;
56-
import com.clickhouse.data.value.UnsignedShort;
57-
58-
import org.roaringbitmap.longlong.Roaring64NavigableMap;
59-
import org.testcontainers.shaded.org.checkerframework.checker.units.qual.A;
60-
import org.testng.Assert;
61-
import org.testng.SkipException;
62-
import org.testng.annotations.DataProvider;
63-
import org.testng.annotations.Test;
64-
6560
public class ClickHouseStatementTest extends JdbcIntegrationTest {
6661
@DataProvider(name = "timeZoneTestOptions")
6762
private Object[][] getTimeZoneTestOptions() {
@@ -1446,4 +1441,40 @@ public void testMultiThreadedExecution() throws Exception {
14461441
Assert.assertNull(failedException.get(), "Failed because of exception: " + failedException.get());
14471442
}
14481443
}
1444+
1445+
@Test(groups = "integration")
1446+
public void testSessionTimezoneSetting() {
1447+
Properties props = new Properties();
1448+
try (ClickHouseConnection conn = newConnection(props);
1449+
ClickHouseStatement stmt = conn.createStatement()) {
1450+
ResultSet rs = stmt.executeQuery("SELECT now() SETTINGS session_timezone = 'America/Los_Angeles'");
1451+
rs.next();
1452+
OffsetDateTime srvNow = rs.getObject(1, OffsetDateTime.class);
1453+
OffsetDateTime localNow = OffsetDateTime.now(ZoneId.of("America/Los_Angeles"));
1454+
Assert.assertTrue(Duration.between(srvNow, localNow).abs().getSeconds() < 60,
1455+
"server time (" + srvNow +") differs from local time (" + localNow + ")");
1456+
} catch (Exception e) {
1457+
Assert.fail("Failed to create connection", e);
1458+
}
1459+
}
1460+
1461+
1462+
@Test(groups = "integration")
1463+
public void testUseOffsetDateTime() {
1464+
try (ClickHouseConnection conn = newConnection();
1465+
ClickHouseStatement stmt = conn.createStatement()) {
1466+
ResultSet rs = stmt.executeQuery("select toDateTime('2024-01-01 10:00:00', 'America/Los_Angeles'), toDateTime('2024-05-01 10:00:00', " +
1467+
" 'America/Los_Angeles'), now() SETTINGS session_timezone = 'America/Los_Angeles'");
1468+
rs.next();
1469+
OffsetDateTime dstStart = (OffsetDateTime) rs.getObject(1);
1470+
OffsetDateTime dstEnd = (OffsetDateTime) rs.getObject(2);
1471+
OffsetDateTime now = rs.getObject(3, OffsetDateTime.class);
1472+
System.out.println("dstStart: " + dstStart + ", dstEnd: " + dstEnd + ", now: " + now);
1473+
Assert.assertEquals(dstStart.getOffset(), ZoneOffset.ofHours(-8));
1474+
Assert.assertEquals(dstEnd.getOffset(), ZoneOffset.ofHours(-7));
1475+
} catch (Exception e) {
1476+
e.printStackTrace();
1477+
Assert.fail("Failed to create connection", e);
1478+
}
1479+
}
14491480
}

0 commit comments

Comments
 (0)