Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusting date/time handling #2097

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,20 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.TimeZone;

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

@Override
public void setDate(int parameterIndex, Date x) throws SQLException {
checkClosed();
setDate(parameterIndex, x, null);
}

@Override
public void setTime(int parameterIndex, Time x) throws SQLException {
checkClosed();
setTime(parameterIndex, x, null);
}

@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
checkClosed();
setTimestamp(parameterIndex, x, null);
}

Expand Down Expand Up @@ -269,42 +269,42 @@ public ResultSetMetaData getMetaData() throws SQLException {
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
checkClosed();
if (cal == null) {
cal = new GregorianCalendar();
cal.setTime(x);
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));//This says whatever date is in UTC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have default for it

}

ZoneId tz = cal.getTimeZone().toZoneId();
LocalDate d = x.toLocalDate();
Calendar c = (Calendar) cal.clone();
c.setTime(x);
parameters[parameterIndex - 1] = encodeObject(c.toInstant().atZone(tz).toLocalDate());
c.clear();
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
parameters[parameterIndex - 1] = encodeObject(c.toInstant());
}

@Override
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
checkClosed();
if (cal == null) {
cal = new GregorianCalendar();
cal.setTime(x);
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
}

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

@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
checkClosed();
if (cal == null) {
cal = new GregorianCalendar();
cal.setTime(x);
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
}

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

@Override
Expand Down Expand Up @@ -479,6 +479,10 @@ private static String encodeObject(Object x) throws SQLException {
return "'" + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + "'";
} else if (x instanceof LocalDateTime) {
return "'" + DATETIME_FORMATTER.format((LocalDateTime) x) + "'";
} else if (x instanceof ZonedDateTime) {
return encodeObject(((ZonedDateTime) x).toInstant());
} else if (x instanceof Instant) {
return "fromUnixTimestamp64Nano(" + (((Instant) x).getEpochSecond() * 1_000_000_000L + ((Instant) x).getNano())+ ")";
} else if (x instanceof Array) {
StringBuilder listString = new StringBuilder();
listString.append("[");
Expand Down Expand Up @@ -571,6 +575,7 @@ private static String encodeObject(Object x) throws SQLException {
}
}


private static String escapeString(String x) {
return x.replace("\\", "\\\\").replace("'", "\\'");//Escape single quotes
}
Expand Down
63 changes: 29 additions & 34 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Map;
Expand Down Expand Up @@ -179,12 +182,12 @@ public byte[] getBytes(int columnIndex) throws SQLException {

@Override
public Date getDate(int columnIndex) throws SQLException {
return getDate(columnIndexToName(columnIndex));
return getDate(columnIndex, null);
}

@Override
public Time getTime(int columnIndex) throws SQLException {
return getTime(columnIndexToName(columnIndex));
return getTime(columnIndex, null);
}

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

@Override
public Date getDate(String columnLabel) throws SQLException {
checkClosed();
try {
//TODO: Add this to ClickHouseBinaryFormatReader
LocalDate localDate = reader.getLocalDate(columnLabel);
if (localDate == null) {
wasNull = true;
return null;
}

wasNull = false;
return Date.valueOf(localDate);
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getDate(%s)", parentStatement.getLastSql(), columnLabel), e);
}
return getDate(columnLabel, null);
}

@Override
public Time getTime(String columnLabel) throws SQLException {
checkClosed();
try {
LocalDateTime localDateTime = reader.getLocalDateTime(columnLabel);
if(localDateTime == null) {
wasNull = true;
return null;
}

wasNull = false;
return Time.valueOf(localDateTime.toLocalTime());
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTime(%s)", parentStatement.getLastSql(), columnLabel), e);
}
return getTime(columnLabel, null);
}

@Override
Expand Down Expand Up @@ -1068,11 +1046,13 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException {
@Override
public Date getDate(String columnLabel, Calendar cal) throws SQLException {
checkClosed();
Date date = getDate(columnLabel);
if (date == null) {
LocalDate d = reader.getLocalDate(columnLabel);
if (d == null) {
wasNull = true;
return null;
}
LocalDate d = date.toLocalDate();
wasNull = false;

Calendar c = (Calendar)( cal != null ? cal : defaultCalendar).clone();
c.clear();
c.set(d.getYear(), d.getMonthValue() - 1, d.getDayOfMonth(), 0, 0, 0);
Expand All @@ -1087,7 +1067,21 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException {
@Override
public Time getTime(String columnLabel, Calendar cal) throws SQLException {
checkClosed();
return getTime(columnLabel);
try {
ZonedDateTime zdt = reader.getZonedDateTime(columnLabel);
if (zdt == null) {
wasNull = true;
return null;
}
wasNull = false;

Calendar c = (Calendar)( cal != null ? cal : defaultCalendar).clone();
c.clear();
c.set(1970, Calendar.JANUARY, 1, zdt.getHour(), zdt.getMinute(), zdt.getSecond());
return new Time(c.getTimeInMillis());
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTime(%s, %s)", parentStatement.getLastSql(), columnLabel, cal), e);
}
}

@Override
Expand All @@ -1104,12 +1098,13 @@ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLExcept
wasNull = true;
return null;
}
wasNull = false;

Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone();
c.set(localDateTime.getYear(), localDateTime.getMonthValue() - 1, localDateTime.getDayOfMonth(), localDateTime.getHour(), localDateTime.getMinute(),
localDateTime.getSecond());
Timestamp timestamp = new Timestamp(c.getTimeInMillis());
timestamp.setNanos(localDateTime.getNano());
wasNull = false;
return timestamp;
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getTimestamp(%s)", parentStatement.getLastSql(), columnLabel), e);
Expand Down
54 changes: 28 additions & 26 deletions jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.TimeZone;
import java.util.UUID;

import static org.testng.Assert.assertEquals;
Expand Down Expand Up @@ -279,31 +281,31 @@ public void testDateTypes() throws SQLException {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_dates ORDER BY order")) {
assertTrue(rs.next());
assertEquals(rs.getDate("date"), Date.valueOf("1970-01-01"));
assertEquals(rs.getDate("date32"), Date.valueOf("1970-01-01"));
assertEquals(rs.getTimestamp("dateTime"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
assertEquals(rs.getTimestamp("dateTime32"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
assertEquals(rs.getTimestamp("dateTime643"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
assertEquals(rs.getTimestamp("dateTime646"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
assertEquals(rs.getTimestamp("dateTime649"), new java.sql.Timestamp(Date.valueOf("1970-01-01").getTime()));
assertEquals(rs.getDate("date", new GregorianCalendar()), Date.valueOf("1970-01-01"));
assertEquals(rs.getDate("date32", new GregorianCalendar()), Date.valueOf("1970-01-01"));
assertEquals(rs.getTimestamp("dateTime").toInstant().toString(), "1970-01-01T00:00:00Z");
assertEquals(rs.getTimestamp("dateTime32").toInstant().toString(), "1970-01-01T00:00:00Z");
assertEquals(rs.getTimestamp("dateTime643").toInstant().toString(), "1970-01-01T00:00:00Z");
assertEquals(rs.getTimestamp("dateTime646").toInstant().toString(), "1970-01-01T00:00:00Z");
assertEquals(rs.getTimestamp("dateTime649").toInstant().toString(), "1970-01-01T00:00:00Z");

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

assertTrue(rs.next());
assertEquals(rs.getDate("date").toLocalDate(), date.toLocalDate());
assertEquals(rs.getDate("date32").toLocalDate(), date32.toLocalDate());
assertEquals(rs.getTimestamp("dateTime"), dateTime);
assertEquals(rs.getTimestamp("dateTime32"), dateTime32);
assertEquals(rs.getTimestamp("dateTime643"), dateTime643);
assertEquals(rs.getTimestamp("dateTime646"), dateTime646);
assertEquals(rs.getTimestamp("dateTime649"), dateTime649);
assertEquals(rs.getDate("date", new GregorianCalendar()).toString(), date.toString());
assertEquals(rs.getDate("date32", new GregorianCalendar()).toString(), date32.toString());
assertEquals(rs.getTimestamp("dateTime", new GregorianCalendar()).toString(), dateTime.toString());
assertEquals(rs.getTimestamp("dateTime32", new GregorianCalendar()).toString(), dateTime32.toString());
assertEquals(rs.getTimestamp("dateTime643", new GregorianCalendar()).toString(), dateTime643.toString());
assertEquals(rs.getTimestamp("dateTime646", new GregorianCalendar()).toString(), dateTime646.toString());
assertEquals(rs.getTimestamp("dateTime649", new GregorianCalendar()).toString(), dateTime649.toString());

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

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

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

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

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

assertEquals(rs.getTimestamp(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789"));
assertEquals(rs.getTimestamp(8).toInstant().toString(), "2024-12-01T12:34:56.789789789Z");
assertTrue(rs.getObject(8) instanceof Timestamp);
assertEquals(rs.getObject(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789"));
assertEquals(rs.getString(8), "2024-12-01T12:34:56.789789789Z[UTC]");
Expand Down
Loading
Loading