Skip to content

Commit 4807241

Browse files
authored
Merge pull request #2107 from ClickHouse/timezone-testing
Timezone testing
2 parents 2581f3b + 3f57bc5 commit 4807241

File tree

7 files changed

+237
-78
lines changed

7 files changed

+237
-78
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.clickhouse.jdbc.comparison;
2+
3+
import com.clickhouse.client.ClickHouseProtocol;
4+
import com.clickhouse.client.ClickHouseServerForTest;
5+
import com.clickhouse.client.api.ClientConfigProperties;
6+
import com.clickhouse.jdbc.ConnectionImpl;
7+
import com.clickhouse.jdbc.JdbcIntegrationTest;
8+
import com.clickhouse.jdbc.internal.ClickHouseConnectionImpl;
9+
import org.testng.Assert;
10+
import org.testng.annotations.Test;
11+
12+
import java.sql.Connection;
13+
import java.sql.Date;
14+
import java.sql.PreparedStatement;
15+
import java.sql.ResultSet;
16+
import java.sql.SQLException;
17+
import java.sql.Statement;
18+
import java.sql.Timestamp;
19+
import java.util.GregorianCalendar;
20+
import java.util.Properties;
21+
import java.util.TimeZone;
22+
23+
import static org.testng.Assert.assertEquals;
24+
import static org.testng.Assert.assertFalse;
25+
import static org.testng.Assert.assertThrows;
26+
import static org.testng.Assert.assertTrue;
27+
28+
public class DateTimeComparisonTest extends JdbcIntegrationTest {
29+
public Connection getJdbcConnectionV1(Properties properties) throws SQLException {
30+
Properties info = new Properties();
31+
info.setProperty("user", "default");
32+
info.setProperty("password", ClickHouseServerForTest.getPassword());
33+
info.setProperty("database", ClickHouseServerForTest.getDatabase());
34+
35+
if (properties != null) {
36+
info.putAll(properties);
37+
}
38+
39+
return new ClickHouseConnectionImpl(getJDBCEndpointString(), info);
40+
}
41+
42+
public Connection getJdbcConnectionV2(Properties properties) throws SQLException {
43+
Properties info = new Properties();
44+
info.setProperty("user", "default");
45+
info.setProperty("password", ClickHouseServerForTest.getPassword());
46+
info.setProperty("database", ClickHouseServerForTest.getDatabase());
47+
48+
if (properties != null) {
49+
info.putAll(properties);
50+
}
51+
52+
info.setProperty(ClientConfigProperties.DATABASE.getKey(), ClickHouseServerForTest.getDatabase());
53+
54+
return new ConnectionImpl(getJDBCEndpointString(), info);
55+
}
56+
57+
public String getJDBCEndpointString() {
58+
return "jdbc:ch:" + (isCloud() ? "" : "http://") +
59+
ClickHouseServerForTest.getClickHouseAddress(ClickHouseProtocol.HTTP, false) + "/" + (isCloud() ? ClickHouseServerForTest.getDatabase() : "");
60+
}
61+
62+
private void run(String query) throws SQLException {
63+
try (Connection connection = getJdbcConnectionV2(null)) {
64+
try (Statement stmt = connection.createStatement()) {
65+
stmt.execute("CREATE DATABASE IF NOT EXISTS " + ClickHouseServerForTest.getDatabase());
66+
stmt.execute(query);
67+
}
68+
}
69+
}
70+
71+
@Test (groups = "integration", enabled = true)
72+
public void setDateTest() throws SQLException {
73+
run("DROP TABLE IF EXISTS test_date");
74+
run("CREATE TABLE IF NOT EXISTS test_date (id Int8, d1 Date, d2 Date, d3 Date) ENGINE = MergeTree ORDER BY id");
75+
76+
try (Connection connV1 = getJdbcConnectionV1(null)) {
77+
try (PreparedStatement stmtV1 = connV1.prepareStatement("INSERT INTO test_date VALUES (1, ?, ?, ?)")) {//INSERT with V1
78+
stmtV1.setDate(1, java.sql.Date.valueOf("2021-01-01"));
79+
stmtV1.setDate(2, java.sql.Date.valueOf("2021-01-01"), new GregorianCalendar());
80+
stmtV1.setDate(3, java.sql.Date.valueOf("2021-01-01"), new GregorianCalendar(TimeZone.getTimeZone("UTC")));
81+
stmtV1.execute();
82+
}
83+
}
84+
85+
try (Connection connV1 = getJdbcConnectionV1(null);
86+
Connection connV2 = getJdbcConnectionV2(null)) {
87+
try (Statement stmtV1 = connV1.createStatement();
88+
Statement stmtV2 = connV2.createStatement()) {
89+
ResultSet rsV1 = stmtV1.executeQuery("SELECT * FROM test_date");
90+
ResultSet rsV2 = stmtV2.executeQuery("SELECT * FROM test_date");
91+
assertTrue(rsV1.next());
92+
assertTrue(rsV2.next());
93+
94+
assertEquals(rsV2.getDate(2), rsV1.getDate(2));
95+
assertEquals(rsV2.getDate(3, new GregorianCalendar()), rsV1.getDate(3, new GregorianCalendar()));
96+
assertEquals(rsV2.getDate(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))),
97+
rsV1.getDate(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))));
98+
}
99+
}
100+
}
101+
102+
103+
@Test (groups = "integration", enabled = true)
104+
public void setTimeTest() throws SQLException {
105+
run("DROP TABLE IF EXISTS test_time");
106+
run("CREATE TABLE IF NOT EXISTS test_time (id Int8, t1 Datetime, t2 Datetime, t3 Datetime) ENGINE = MergeTree ORDER BY id");
107+
108+
try (Connection connV1 = getJdbcConnectionV1(null)) {
109+
try (PreparedStatement stmtV1 = connV1.prepareStatement("INSERT INTO test_time VALUES (1, ?, ?, ?)")) {//INSERT with V1
110+
stmtV1.setTime(1, java.sql.Time.valueOf("12:34:56"));
111+
stmtV1.setTime(2, java.sql.Time.valueOf("12:34:56"), new GregorianCalendar());
112+
stmtV1.setTime(3, java.sql.Time.valueOf("12:34:56"), new GregorianCalendar(TimeZone.getTimeZone("UTC")));
113+
stmtV1.execute();
114+
}
115+
}
116+
117+
try (Connection connV1 = getJdbcConnectionV1(null);
118+
Connection connV2 = getJdbcConnectionV2(null)) {
119+
try (Statement stmtV1 = connV1.createStatement();
120+
Statement stmtV2 = connV2.createStatement()) {
121+
ResultSet rsV1 = stmtV1.executeQuery("SELECT * FROM test_time");
122+
ResultSet rsV2 = stmtV2.executeQuery("SELECT * FROM test_time");
123+
assertTrue(rsV1.next());
124+
assertTrue(rsV2.next());
125+
126+
assertEquals(rsV2.getTime(2), rsV1.getTime(2));
127+
assertEquals(rsV2.getTime(3, new GregorianCalendar()), rsV1.getTime(3, new GregorianCalendar()));
128+
assertEquals(rsV2.getTime(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))),
129+
rsV1.getTime(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))));
130+
}
131+
}
132+
}
133+
134+
@Test (groups = "integration", enabled = true)
135+
public void setTimestampTest() throws SQLException {
136+
run("DROP TABLE IF EXISTS test_timestamp");
137+
run("CREATE TABLE IF NOT EXISTS test_timestamp (id Int8, t1 Datetime64(3), t2 Datetime64(6), t3 Datetime64(9), t4 DateTime64(9)) ENGINE = MergeTree ORDER BY id");
138+
139+
Timestamp ts = new Timestamp(System.currentTimeMillis());
140+
141+
try (Connection connV1 = getJdbcConnectionV1(null)) {
142+
try (PreparedStatement stmtV1 = connV1.prepareStatement("INSERT INTO test_timestamp VALUES (1, ?, ?, ?, ?)")) {//INSERT with V1
143+
stmtV1.setTimestamp(1, java.sql.Timestamp.valueOf("2021-01-01 01:23:45"));
144+
stmtV1.setTimestamp(2, java.sql.Timestamp.valueOf("2021-01-01 01:23:45"), new GregorianCalendar());
145+
stmtV1.setTimestamp(3, java.sql.Timestamp.valueOf("2021-01-01 01:23:45"), new GregorianCalendar(TimeZone.getTimeZone("UTC")));
146+
stmtV1.setTimestamp(4, ts);
147+
stmtV1.execute();
148+
}
149+
}
150+
151+
try (Connection connV1 = getJdbcConnectionV1(null);
152+
Connection connV2 = getJdbcConnectionV2(null)) {
153+
try (Statement stmtV1 = connV1.createStatement();
154+
Statement stmtV2 = connV2.createStatement()) {
155+
ResultSet rsV1 = stmtV1.executeQuery("SELECT * FROM test_timestamp");
156+
ResultSet rsV2 = stmtV2.executeQuery("SELECT * FROM test_timestamp");
157+
assertTrue(rsV1.next());
158+
assertTrue(rsV2.next());
159+
160+
assertEquals(rsV2.getTimestamp(2), rsV1.getTimestamp(2));
161+
assertEquals(rsV2.getTimestamp(3, new GregorianCalendar()), rsV1.getTimestamp(3, new GregorianCalendar()));
162+
assertEquals(rsV2.getTimestamp(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))),
163+
rsV1.getTimestamp(4, new GregorianCalendar(TimeZone.getTimeZone("UTC"))));
164+
assertEquals(rsV2.getTimestamp(5), rsV1.getTimestamp(5));
165+
}
166+
}
167+
}
168+
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.sql.ShardingKey;
3131
import java.sql.Statement;
3232
import java.sql.Struct;
33+
import java.util.Calendar;
3334
import java.util.HashSet;
3435
import java.util.List;
3536
import java.util.Map;
@@ -52,6 +53,7 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper {
5253
private QuerySettings defaultQuerySettings;
5354

5455
private final com.clickhouse.jdbc.metadata.DatabaseMetaData metadata;
56+
protected final Calendar defaultCalendar;
5557

5658
public ConnectionImpl(String url, Properties info) throws SQLException {
5759
try {
@@ -80,6 +82,7 @@ public ConnectionImpl(String url, Properties info) throws SQLException {
8082
.serverSetting(ServerSettings.WAIT_END_OF_QUERY, "0");
8183

8284
this.metadata = new com.clickhouse.jdbc.metadata.DatabaseMetaData(this, false, url);
85+
this.defaultCalendar = Calendar.getInstance();
8386
} catch (SQLException e) {
8487
throw e;
8588
} catch (Exception e) {

jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java

+10-14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.time.LocalDate;
3333
import java.time.LocalDateTime;
3434
import java.time.LocalTime;
35+
import java.time.OffsetDateTime;
3536
import java.time.ZoneId;
3637
import java.time.ZonedDateTime;
3738
import java.time.format.DateTimeFormatter;
@@ -41,7 +42,6 @@
4142
import java.util.Collection;
4243
import java.util.GregorianCalendar;
4344
import java.util.Map;
44-
import java.util.TimeZone;
4545

4646
public class PreparedStatementImpl extends StatementImpl implements PreparedStatement, JdbcV2Wrapper {
4747
private static final Logger LOG = LoggerFactory.getLogger(PreparedStatementImpl.class);
@@ -52,6 +52,8 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
5252
public static final DateTimeFormatter DATETIME_FORMATTER = new DateTimeFormatterBuilder()
5353
.appendPattern("yyyy-MM-dd HH:mm:ss").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter();
5454

55+
private final Calendar defaultCalendar;
56+
5557
String originalSql;
5658
String [] sqlSegments;
5759
Object [] parameters;
@@ -68,6 +70,8 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx
6870
} else {
6971
this.parameters = new Object[0];
7072
}
73+
74+
this.defaultCalendar = connection.defaultCalendar;
7175
}
7276

7377
private String compileSql() {
@@ -268,12 +272,8 @@ public ResultSetMetaData getMetaData() throws SQLException {
268272
@Override
269273
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
270274
checkClosed();
271-
if (cal == null) {
272-
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));//This says whatever date is in UTC
273-
}
274-
275275
LocalDate d = x.toLocalDate();
276-
Calendar c = (Calendar) cal.clone();
276+
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
277277
c.clear();
278278
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
279279
parameters[parameterIndex - 1] = encodeObject(c.toInstant());
@@ -282,12 +282,9 @@ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLExceptio
282282
@Override
283283
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
284284
checkClosed();
285-
if (cal == null) {
286-
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
287-
}
288285

289286
LocalTime t = x.toLocalTime();
290-
Calendar c = (Calendar) cal.clone();
287+
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
291288
c.clear();
292289
c.set(1970, Calendar.JANUARY, 1, t.getHour(), t.getMinute(), t.getSecond());
293290
parameters[parameterIndex - 1] = encodeObject(c.toInstant());
@@ -296,12 +293,9 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio
296293
@Override
297294
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
298295
checkClosed();
299-
if (cal == null) {
300-
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
301-
}
302296

303297
LocalDateTime ldt = x.toLocalDateTime();
304-
Calendar c = (Calendar) cal.clone();
298+
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
305299
c.clear();
306300
c.set(ldt.getYear(), ldt.getMonthValue() - 1, ldt.getDayOfMonth(), ldt.getHour(), ldt.getMinute(), ldt.getSecond());
307301
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(ZoneId.of("UTC")).withNano(x.getNanos()));
@@ -479,6 +473,8 @@ private static String encodeObject(Object x) throws SQLException {
479473
return "'" + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + "'";
480474
} else if (x instanceof LocalDateTime) {
481475
return "'" + DATETIME_FORMATTER.format((LocalDateTime) x) + "'";
476+
} else if (x instanceof OffsetDateTime) {
477+
return encodeObject(((OffsetDateTime) x).toInstant());
482478
} else if (x instanceof ZonedDateTime) {
483479
return encodeObject(((ZonedDateTime) x).toInstant());
484480
} else if (x instanceof Instant) {

jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java

+12-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
package com.clickhouse.jdbc;
22

33
import java.io.ByteArrayInputStream;
4-
import java.io.CharArrayReader;
54
import java.io.InputStream;
65
import java.io.Reader;
76
import java.io.StringReader;
87
import java.math.BigDecimal;
9-
import java.net.MalformedURLException;
108
import java.net.URL;
119
import java.nio.charset.StandardCharsets;
1210
import java.sql.*;
13-
import java.time.LocalDate;
14-
import java.time.LocalDateTime;
15-
import java.time.LocalTime;
16-
import java.time.ZoneId;
1711
import java.time.ZonedDateTime;
1812
import java.util.Calendar;
1913
import java.util.GregorianCalendar;
2014
import java.util.Map;
21-
import java.util.TimeZone;
2215

2316
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
2417
import com.clickhouse.client.api.metadata.TableSchema;
@@ -47,7 +40,7 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic
4740
this.metaData = new com.clickhouse.jdbc.metadata.ResultSetMetaData(this);
4841
this.closed = false;
4942
this.wasNull = false;
50-
this.defaultCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
43+
this.defaultCalendar = parentStatement.connection.defaultCalendar;
5144
}
5245

5346
protected ResultSetImpl(ResultSetImpl resultSet) {
@@ -57,7 +50,7 @@ protected ResultSetImpl(ResultSetImpl resultSet) {
5750
this.metaData = resultSet.metaData;
5851
this.closed = false;
5952
this.wasNull = false;
60-
this.defaultCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
53+
this.defaultCalendar = parentStatement.connection.defaultCalendar;
6154
}
6255

6356
private void checkClosed() throws SQLException {
@@ -1048,16 +1041,16 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException {
10481041
public Date getDate(String columnLabel, Calendar cal) throws SQLException {
10491042
checkClosed();
10501043
try {
1051-
LocalDate d = reader.getLocalDate(columnLabel);
1052-
if (d == null) {
1044+
ZonedDateTime zdt = reader.getZonedDateTime(columnLabel);
1045+
if (zdt == null) {
10531046
wasNull = true;
10541047
return null;
10551048
}
10561049
wasNull = false;
10571050

10581051
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
10591052
c.clear();
1060-
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
1053+
c.set(zdt.getYear(), zdt.getMonthValue() - 1, zdt.getDayOfMonth(), 0, 0, 0);
10611054
return new Date(c.getTimeInMillis());
10621055
} catch (Exception e) {
10631056
throw ExceptionUtils.toSqlState(String.format("Method: getDate(\"%s\") encountered an exception.", columnLabel), String.format("SQL: [%s]", parentStatement.getLastSql()), e);
@@ -1080,7 +1073,7 @@ public Time getTime(String columnLabel, Calendar cal) throws SQLException {
10801073
}
10811074
wasNull = false;
10821075

1083-
Calendar c = (Calendar)( cal != null ? cal : defaultCalendar).clone();
1076+
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
10841077
c.clear();
10851078
c.set(1970, Calendar.JANUARY, 1, zdt.getHour(), zdt.getMinute(), zdt.getSecond());
10861079
return new Time(c.getTimeInMillis());
@@ -1098,23 +1091,22 @@ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException
10981091
public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
10991092
checkClosed();
11001093
try {
1101-
LocalDateTime localDateTime = reader.getLocalDateTime(columnLabel);
1102-
if (localDateTime == null) {
1094+
ZonedDateTime zdt = reader.getZonedDateTime(columnLabel);
1095+
if (zdt == null) {
11031096
wasNull = true;
11041097
return null;
11051098
}
11061099
wasNull = false;
1107-
1100+
11081101
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
1109-
c.set(localDateTime.getYear(), localDateTime.getMonthValue() - 1, localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(),
1110-
localDateTime.getSecond());
1102+
c.set(zdt.getYear(), zdt.getMonthValue() - 1, zdt.getDayOfMonth(), zdt.getHour(), zdt.getMinute(),
1103+
zdt.getSecond());
11111104
Timestamp timestamp = new Timestamp(c.getTimeInMillis());
1112-
timestamp.setNanos(localDateTime.getNano());
1105+
timestamp.setNanos(zdt.getNano());
11131106
return timestamp;
11141107
} catch (Exception e) {
11151108
throw ExceptionUtils.toSqlState(String.format("Method: getTimestamp(\"%s\") encountered an exception.", columnLabel), String.format("SQL: [%s]", parentStatement.getLastSql()), e);
11161109
}
1117-
11181110
}
11191111

11201112
@Override

0 commit comments

Comments
 (0)