Skip to content

Commit c1478f9

Browse files
authored
Merge pull request #2097 from ClickHouse/handle-datetimes-with-calendar
2 parents f085ddd + 9be2eb5 commit c1478f9

File tree

5 files changed

+101
-110
lines changed

5 files changed

+101
-110
lines changed

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,20 @@
2828
import java.sql.Time;
2929
import java.sql.Timestamp;
3030
import java.sql.Types;
31+
import java.time.Instant;
3132
import java.time.LocalDate;
3233
import java.time.LocalDateTime;
3334
import java.time.LocalTime;
3435
import java.time.ZoneId;
36+
import java.time.ZonedDateTime;
3537
import java.time.format.DateTimeFormatter;
3638
import java.time.format.DateTimeFormatterBuilder;
3739
import java.time.temporal.ChronoField;
3840
import java.util.Calendar;
3941
import java.util.Collection;
4042
import java.util.GregorianCalendar;
4143
import java.util.Map;
44+
import java.util.TimeZone;
4245

4346
public class PreparedStatementImpl extends StatementImpl implements PreparedStatement, JdbcV2Wrapper {
4447
private static final Logger LOG = LoggerFactory.getLogger(PreparedStatementImpl.class);
@@ -159,19 +162,16 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException {
159162

160163
@Override
161164
public void setDate(int parameterIndex, Date x) throws SQLException {
162-
checkClosed();
163165
setDate(parameterIndex, x, null);
164166
}
165167

166168
@Override
167169
public void setTime(int parameterIndex, Time x) throws SQLException {
168-
checkClosed();
169170
setTime(parameterIndex, x, null);
170171
}
171172

172173
@Override
173174
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
174-
checkClosed();
175175
setTimestamp(parameterIndex, x, null);
176176
}
177177

@@ -269,42 +269,42 @@ public ResultSetMetaData getMetaData() throws SQLException {
269269
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
270270
checkClosed();
271271
if (cal == null) {
272-
cal = new GregorianCalendar();
273-
cal.setTime(x);
272+
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));//This says whatever date is in UTC
274273
}
275274

276-
ZoneId tz = cal.getTimeZone().toZoneId();
275+
LocalDate d = x.toLocalDate();
277276
Calendar c = (Calendar) cal.clone();
278-
c.setTime(x);
279-
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(tz).toLocalDate());
277+
c.clear();
278+
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
279+
parameters[parameterIndex - 1] = encodeObject(c.toInstant());
280280
}
281281

282282
@Override
283283
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
284284
checkClosed();
285285
if (cal == null) {
286-
cal = new GregorianCalendar();
287-
cal.setTime(x);
286+
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
288287
}
289288

290-
ZoneId tz = cal.getTimeZone().toZoneId();
289+
LocalTime t = x.toLocalTime();
291290
Calendar c = (Calendar) cal.clone();
292-
c.setTime(x);
293-
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(tz).toLocalTime());
291+
c.clear();
292+
c.set(1970, Calendar.JANUARY, 1, t.getHour(), t.getMinute(), t.getSecond());
293+
parameters[parameterIndex - 1] = encodeObject(c.toInstant());
294294
}
295295

296296
@Override
297297
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
298298
checkClosed();
299299
if (cal == null) {
300-
cal = new GregorianCalendar();
301-
cal.setTime(x);
300+
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
302301
}
303302

304-
ZoneId tz = cal.getTimeZone().toZoneId();
303+
LocalDateTime ldt = x.toLocalDateTime();
305304
Calendar c = (Calendar) cal.clone();
306-
c.setTime(x);
307-
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(tz).withNano(x.getNanos()).toLocalDateTime());
305+
c.clear();
306+
c.set(ldt.getYear(), ldt.getMonthValue() - 1, ldt.getDayOfMonth(), ldt.getHour(), ldt.getMinute(), ldt.getSecond());
307+
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(ZoneId.of("UTC")).withNano(x.getNanos()));
308308
}
309309

310310
@Override
@@ -479,6 +479,10 @@ private static String encodeObject(Object x) throws SQLException {
479479
return "'" + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + "'";
480480
} else if (x instanceof LocalDateTime) {
481481
return "'" + DATETIME_FORMATTER.format((LocalDateTime) x) + "'";
482+
} else if (x instanceof ZonedDateTime) {
483+
return encodeObject(((ZonedDateTime) x).toInstant());
484+
} else if (x instanceof Instant) {
485+
return "fromUnixTimestamp64Nano(" + (((Instant) x).getEpochSecond() * 1_000_000_000L + ((Instant) x).getNano())+ ")";
482486
} else if (x instanceof Array) {
483487
StringBuilder listString = new StringBuilder();
484488
listString.append("[");
@@ -571,6 +575,7 @@ private static String encodeObject(Object x) throws SQLException {
571575
}
572576
}
573577

578+
574579
private static String escapeString(String x) {
575580
return x.replace("\\", "\\\\").replace("'", "\\'");//Escape single quotes
576581
}

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

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import java.sql.*;
1212
import java.time.LocalDate;
1313
import java.time.LocalDateTime;
14+
import java.time.LocalTime;
15+
import java.time.ZoneId;
16+
import java.time.ZonedDateTime;
1417
import java.util.Calendar;
1518
import java.util.GregorianCalendar;
1619
import java.util.Map;
@@ -179,12 +182,12 @@ public byte[] getBytes(int columnIndex) throws SQLException {
179182

180183
@Override
181184
public Date getDate(int columnIndex) throws SQLException {
182-
return getDate(columnIndexToName(columnIndex));
185+
return getDate(columnIndex, null);
183186
}
184187

185188
@Override
186189
public Time getTime(int columnIndex) throws SQLException {
187-
return getTime(columnIndexToName(columnIndex));
190+
return getTime(columnIndex, null);
188191
}
189192

190193
@Override
@@ -369,37 +372,12 @@ public byte[] getBytes(String columnLabel) throws SQLException {
369372

370373
@Override
371374
public Date getDate(String columnLabel) throws SQLException {
372-
checkClosed();
373-
try {
374-
//TODO: Add this to ClickHouseBinaryFormatReader
375-
LocalDate localDate = reader.getLocalDate(columnLabel);
376-
if (localDate == null) {
377-
wasNull = true;
378-
return null;
379-
}
380-
381-
wasNull = false;
382-
return Date.valueOf(localDate);
383-
} catch (Exception e) {
384-
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getDate(%s)", parentStatement.getLastSql(), columnLabel), e);
385-
}
375+
return getDate(columnLabel, null);
386376
}
387377

388378
@Override
389379
public Time getTime(String columnLabel) throws SQLException {
390-
checkClosed();
391-
try {
392-
LocalDateTime localDateTime = reader.getLocalDateTime(columnLabel);
393-
if(localDateTime == null) {
394-
wasNull = true;
395-
return null;
396-
}
397-
398-
wasNull = false;
399-
return Time.valueOf(localDateTime.toLocalTime());
400-
} catch (Exception e) {
401-
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTime(%s)", parentStatement.getLastSql(), columnLabel), e);
402-
}
380+
return getTime(columnLabel, null);
403381
}
404382

405383
@Override
@@ -1068,11 +1046,13 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException {
10681046
@Override
10691047
public Date getDate(String columnLabel, Calendar cal) throws SQLException {
10701048
checkClosed();
1071-
Date date = getDate(columnLabel);
1072-
if (date == null) {
1049+
LocalDate d = reader.getLocalDate(columnLabel);
1050+
if (d == null) {
1051+
wasNull = true;
10731052
return null;
10741053
}
1075-
LocalDate d = date.toLocalDate();
1054+
wasNull = false;
1055+
10761056
Calendar c = (Calendar)( cal != null ? cal : defaultCalendar).clone();
10771057
c.clear();
10781058
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
@@ -1087,7 +1067,21 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException {
10871067
@Override
10881068
public Time getTime(String columnLabel, Calendar cal) throws SQLException {
10891069
checkClosed();
1090-
return getTime(columnLabel);
1070+
try {
1071+
ZonedDateTime zdt = reader.getZonedDateTime(columnLabel);
1072+
if (zdt == null) {
1073+
wasNull = true;
1074+
return null;
1075+
}
1076+
wasNull = false;
1077+
1078+
Calendar c = (Calendar)( cal != null ? cal : defaultCalendar).clone();
1079+
c.clear();
1080+
c.set(1970, Calendar.JANUARY, 1, zdt.getHour(), zdt.getMinute(), zdt.getSecond());
1081+
return new Time(c.getTimeInMillis());
1082+
} catch (Exception e) {
1083+
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTime(%s, %s)", parentStatement.getLastSql(), columnLabel, cal), e);
1084+
}
10911085
}
10921086

10931087
@Override
@@ -1104,12 +1098,13 @@ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLExcept
11041098
wasNull = true;
11051099
return null;
11061100
}
1101+
wasNull = false;
1102+
11071103
Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
11081104
c.set(localDateTime.getYear(), localDateTime.getMonthValue() - 1, localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(),
11091105
localDateTime.getSecond());
11101106
Timestamp timestamp = new Timestamp(c.getTimeInMillis());
11111107
timestamp.setNanos(localDateTime.getNano());
1112-
wasNull = false;
11131108
return timestamp;
11141109
} catch (Exception e) {
11151110
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTimestamp(%s)", parentStatement.getLastSql(), columnLabel), e);

jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
import java.time.OffsetDateTime;
2424
import java.time.ZoneId;
2525
import java.time.ZonedDateTime;
26+
import java.util.GregorianCalendar;
2627
import java.util.HashMap;
2728
import java.util.Map;
2829
import java.util.Properties;
2930
import java.util.Random;
31+
import java.util.TimeZone;
3032
import java.util.UUID;
3133

3234
import static org.testng.Assert.assertEquals;
@@ -279,31 +281,31 @@ public void testDateTypes() throws SQLException {
279281
try (Statement stmt = conn.createStatement()) {
280282
try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_dates ORDER BY order")) {
281283
assertTrue(rs.next());
282-
assertEquals(rs.getDate("date"), Date.valueOf("1970-01-01"));
283-
assertEquals(rs.getDate("date32"), Date.valueOf("1970-01-01"));
284-
assertEquals(rs.getTimestamp("dateTime"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
285-
assertEquals(rs.getTimestamp("dateTime32"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
286-
assertEquals(rs.getTimestamp("dateTime643"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
287-
assertEquals(rs.getTimestamp("dateTime646"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
288-
assertEquals(rs.getTimestamp("dateTime649"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
284+
assertEquals(rs.getDate("date", new GregorianCalendar()), Date.valueOf("1970-01-01"));
285+
assertEquals(rs.getDate("date32", new GregorianCalendar()), Date.valueOf("1970-01-01"));
286+
assertEquals(rs.getTimestamp("dateTime").toInstant().toString(), "1970-01-01T00:00:00Z");
287+
assertEquals(rs.getTimestamp("dateTime32").toInstant().toString(), "1970-01-01T00:00:00Z");
288+
assertEquals(rs.getTimestamp("dateTime643").toInstant().toString(), "1970-01-01T00:00:00Z");
289+
assertEquals(rs.getTimestamp("dateTime646").toInstant().toString(), "1970-01-01T00:00:00Z");
290+
assertEquals(rs.getTimestamp("dateTime649").toInstant().toString(), "1970-01-01T00:00:00Z");
289291

290292
assertTrue(rs.next());
291-
assertEquals(rs.getDate("date"), Date.valueOf("2149-06-06"));
292-
assertEquals(rs.getDate("date32"), Date.valueOf("2299-12-31"));
293-
assertEquals(rs.getTimestamp("dateTime"), java.sql.Timestamp.valueOf("2106-02-07 06:28:15"));
294-
assertEquals(rs.getTimestamp("dateTime32"), java.sql.Timestamp.valueOf("2106-02-07 06:28:15"));
295-
assertEquals(rs.getTimestamp("dateTime643"), java.sql.Timestamp.valueOf("2261-12-31 23:59:59.999"));
296-
assertEquals(rs.getTimestamp("dateTime646"), java.sql.Timestamp.valueOf("2261-12-31 23:59:59.999999"));
297-
assertEquals(rs.getTimestamp("dateTime649"), java.sql.Timestamp.valueOf("2261-12-31 23:59:59.999999999"));
293+
assertEquals(rs.getDate("date", new GregorianCalendar()), Date.valueOf("2149-06-06"));
294+
assertEquals(rs.getDate("date32", new GregorianCalendar()), Date.valueOf("2299-12-31"));
295+
assertEquals(rs.getTimestamp("dateTime").toInstant().toString(), "2106-02-07T06:28:15Z");
296+
assertEquals(rs.getTimestamp("dateTime32").toInstant().toString(), "2106-02-07T06:28:15Z");
297+
assertEquals(rs.getTimestamp("dateTime643").toInstant().toString(), "2261-12-31T23:59:59.999Z");
298+
assertEquals(rs.getTimestamp("dateTime646").toInstant().toString(), "2261-12-31T23:59:59.999999Z");
299+
assertEquals(rs.getTimestamp("dateTime649").toInstant().toString(), "2261-12-31T23:59:59.999999999Z");
298300

299301
assertTrue(rs.next());
300-
assertEquals(rs.getDate("date").toLocalDate(), date.toLocalDate());
301-
assertEquals(rs.getDate("date32").toLocalDate(), date32.toLocalDate());
302-
assertEquals(rs.getTimestamp("dateTime"), dateTime);
303-
assertEquals(rs.getTimestamp("dateTime32"), dateTime32);
304-
assertEquals(rs.getTimestamp("dateTime643"), dateTime643);
305-
assertEquals(rs.getTimestamp("dateTime646"), dateTime646);
306-
assertEquals(rs.getTimestamp("dateTime649"), dateTime649);
302+
assertEquals(rs.getDate("date", new GregorianCalendar()).toString(), date.toString());
303+
assertEquals(rs.getDate("date32", new GregorianCalendar()).toString(), date32.toString());
304+
assertEquals(rs.getTimestamp("dateTime", new GregorianCalendar()).toString(), dateTime.toString());
305+
assertEquals(rs.getTimestamp("dateTime32", new GregorianCalendar()).toString(), dateTime32.toString());
306+
assertEquals(rs.getTimestamp("dateTime643", new GregorianCalendar()).toString(), dateTime643.toString());
307+
assertEquals(rs.getTimestamp("dateTime646", new GregorianCalendar()).toString(), dateTime646.toString());
308+
assertEquals(rs.getTimestamp("dateTime649", new GregorianCalendar()).toString(), dateTime649.toString());
307309

308310
assertFalse(rs.next());
309311
}
@@ -885,37 +887,37 @@ public void testTypeConversions() throws Exception {
885887
assertEquals(rs.getObject(3, Double.class), 1.0);
886888
assertEquals(String.valueOf(rs.getObject(3, new HashMap<String, Class<?>>(){{put(JDBCType.FLOAT.getName(), Float.class);}})), "1.0");
887889

888-
assertEquals(rs.getDate(4), Date.valueOf("2024-12-01"));
890+
assertEquals(rs.getDate(4, new GregorianCalendar()), Date.valueOf("2024-12-01"));
889891
assertTrue(rs.getObject(4) instanceof Date);
890892
assertEquals(rs.getObject(4), Date.valueOf("2024-12-01"));
891893
assertEquals(rs.getString(4), "2024-12-01");//Underlying object is ZonedDateTime
892894
assertEquals(rs.getObject(4, LocalDate.class), LocalDate.of(2024, 12, 1));
893895
assertEquals(rs.getObject(4, ZonedDateTime.class), ZonedDateTime.of(2024, 12, 1, 0, 0, 0, 0, ZoneId.of("UTC")));
894896
assertEquals(String.valueOf(rs.getObject(4, new HashMap<String, Class<?>>(){{put(JDBCType.DATE.getName(), LocalDate.class);}})), "2024-12-01");
895897

896-
assertEquals(rs.getTimestamp(5), Timestamp.valueOf("2024-12-01 12:34:56"));
898+
assertEquals(rs.getTimestamp(5).toInstant().toString(), "2024-12-01T12:34:56Z");
897899
assertTrue(rs.getObject(5) instanceof Timestamp);
898900
assertEquals(rs.getObject(5), Timestamp.valueOf("2024-12-01 12:34:56"));
899901
assertEquals(rs.getString(5), "2024-12-01T12:34:56Z[UTC]");
900902
assertEquals(rs.getObject(5, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56));
901903
assertEquals(rs.getObject(5, ZonedDateTime.class), ZonedDateTime.of(2024, 12, 1, 12, 34, 56, 0, ZoneId.of("UTC")));
902904
assertEquals(String.valueOf(rs.getObject(5, new HashMap<String, Class<?>>(){{put(JDBCType.TIMESTAMP.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56");
903905

904-
assertEquals(rs.getTimestamp(6), Timestamp.valueOf("2024-12-01 12:34:56.789"));
906+
assertEquals(rs.getTimestamp(6).toInstant().toString(), "2024-12-01T12:34:56.789Z");
905907
assertTrue(rs.getObject(6) instanceof Timestamp);
906908
assertEquals(rs.getObject(6), Timestamp.valueOf("2024-12-01 12:34:56.789"));
907909
assertEquals(rs.getString(6), "2024-12-01T12:34:56.789Z[UTC]");
908910
assertEquals(rs.getObject(6, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789000000));
909911
assertEquals(String.valueOf(rs.getObject(6, new HashMap<String, Class<?>>(){{put(JDBCType.TIMESTAMP.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56.789");
910912

911-
assertEquals(rs.getTimestamp(7), Timestamp.valueOf("2024-12-01 12:34:56.789789"));
913+
assertEquals(rs.getTimestamp(7).toInstant().toString(), "2024-12-01T12:34:56.789789Z");
912914
assertTrue(rs.getObject(7) instanceof Timestamp);
913915
assertEquals(rs.getObject(7), Timestamp.valueOf("2024-12-01 12:34:56.789789"));
914916
assertEquals(rs.getString(7), "2024-12-01T12:34:56.789789Z[UTC]");
915917
assertEquals(rs.getObject(7, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789000));
916918
assertEquals(String.valueOf(rs.getObject(7, new HashMap<String, Class<?>>(){{put(JDBCType.TIMESTAMP.getName(), OffsetDateTime.class);}})), "2024-12-01T12:34:56.789789Z");
917919

918-
assertEquals(rs.getTimestamp(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789"));
920+
assertEquals(rs.getTimestamp(8).toInstant().toString(), "2024-12-01T12:34:56.789789789Z");
919921
assertTrue(rs.getObject(8) instanceof Timestamp);
920922
assertEquals(rs.getObject(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789"));
921923
assertEquals(rs.getString(8), "2024-12-01T12:34:56.789789789Z[UTC]");

0 commit comments

Comments
 (0)