diff --git a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java index a52a86611..99de854e7 100644 --- a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java +++ b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java @@ -129,8 +129,8 @@ public enum ClickHouseDataType implements SQLType { AggregateFunction(String.class, true, true, false, 0, 0, 0, 0, 0, true), Variant(List.class, true, true, false, 0, 0, 0, 0, 0, true, 0x2A), Dynamic(Object.class, true, true, false, 0, 0, 0, 0, 0, true, 0x2B), - Time(LocalDateTime.class, true, false, false, 4, 9, 0, 0, 9, false, 0x32), // 0x33 for Time(Timezone) - Time64(LocalDateTime.class, true, false, false, 8, 9, 0, 0, 0, false, 0x34), // 0x35 for Time64(P, Timezone) + Time(LocalDateTime.class, false, false, false, 4, 9, 0, 0, 9, false, 0x32), + Time64(LocalDateTime.class, true, false, false, 8, 9, 0, 0, 0, false, 0x34), ; public static final List ORDERED_BY_RANGE_INT_TYPES = diff --git a/client-v2/src/main/java/com/clickhouse/client/api/DataTypeUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/DataTypeUtils.java index eed908202..509e6ff00 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/DataTypeUtils.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/DataTypeUtils.java @@ -1,18 +1,22 @@ package com.clickhouse.client.api; -import java.time.Instant; -import java.time.ZoneId; import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; +import com.clickhouse.data.ClickHouseDataType; +import java.sql.Time; +import java.sql.Timestamp; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; +import java.time.temporal.Temporal; +import java.util.Date; import java.util.Objects; -import com.clickhouse.data.ClickHouseDataType; - import static com.clickhouse.client.api.data_formats.internal.BinaryStreamReader.BASES; public class DataTypeUtils { @@ -39,6 +43,19 @@ public class DataTypeUtils { .appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true) .toFormatter(); + public static DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder() + .appendFraction(ChronoField.HOUR_OF_DAY, 1, 4, false) + .appendFraction(ChronoField.MINUTE_OF_HOUR, 1, 3, false) + .appendFraction(ChronoField.SECOND_OF_MINUTE, 1, 3, false) + .toFormatter(); + + public static DateTimeFormatter TIME_WITH_NANOS_FORMATTER = new DateTimeFormatterBuilder() + .appendFraction(ChronoField.HOUR_OF_DAY, 1, 4, false) + .appendFraction(ChronoField.MINUTE_OF_HOUR, 1, 3, false) + .appendFraction(ChronoField.SECOND_OF_MINUTE, 1, 3, false) + .appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true) + .toFormatter(); + /** * Formats an {@link Instant} object for use in SQL statements or as query * parameter. @@ -138,4 +155,60 @@ public static Instant instantFromTime64Integer(int precision, long value) { return Instant.ofEpochSecond(value, nanoSeconds); } + + /** + * Converts a Java object to a temporal accessor suitable for date only operations. + * It accepts Time and Date and converts to LocalDateTime + * @param date + * @return + */ + public static Temporal toLocalDateTemporal(Object date) { + if (date instanceof Temporal) { + return (Temporal) date; + } else if (date instanceof java.sql.Date) { + return ((java.sql.Date) date).toLocalDate(); + } else if (date instanceof Timestamp) { + return ((java.sql.Timestamp) date).toLocalDateTime().toLocalDate(); + } else if (date instanceof java.util.Date) { + return ((java.util.Date) date).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + throw new IllegalArgumentException("Object of type '" + date.getClass() + "' cannot be converted to local date because has no date part"); + } + + /** + * Converts a Java object to a temporal accessor suitable for date and time operations. + * @param date + * @return + */ + public static Temporal toLocalDateTimeTemporal(Object date) { + if (date instanceof Temporal) { + return (Temporal) date; + } else if (date instanceof java.sql.Timestamp) { + return ((java.sql.Timestamp) date).toLocalDateTime(); + } else if (date instanceof java.util.Date) { + return ((java.util.Date) date).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + throw new IllegalArgumentException("Object of type '" + date.getClass() + "' cannot be converted to local date because has no date or time part"); + } + + /** + * Converts a Java object to a temporal accessor suitable for time only operations. + * @param date + * @return + */ + public static Temporal toLocalTimeTemporal(Object date) { + if (date instanceof Temporal) { + return (Temporal) date; + } else if (date instanceof Timestamp) { + return ((Timestamp) date).toLocalDateTime(); + } else if (date instanceof Time) { + return ((Time) date).toLocalTime(); + } else if (date instanceof java.util.Date) { + return ((java.util.Date) date).toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); + } + + throw new IllegalArgumentException("Object of type '" + date.getClass() + "' cannot be converted to local time because has no time part"); + } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java index 3e1d1a7e6..e9a32656e 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java @@ -159,61 +159,61 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException { @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Bool, null); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Int8, null); } @Override public void setShort(int parameterIndex, short x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Int16, null); } @Override public void setInt(int parameterIndex, int x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Int32, null); } @Override public void setLong(int parameterIndex, long x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Int64, null); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Float32, null); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Float64, null); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Decimal, 9L); } @Override public void setString(int parameterIndex, String x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override @@ -240,7 +240,7 @@ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x, (long) length); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, (long) length); } @Override @@ -270,7 +270,7 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { ensureOpen(); // targetSQLType is only of JDBCType - values[parameterIndex-1] = encodeObject(x, jdbcType2ClickHouseDataType(JDBCType.valueOf(targetSqlType)), scaleOrLength); + values[parameterIndex-1] = encodeObject(x, jdbcType2ClickHouseDataType(JDBCType.valueOf(targetSqlType)), (long) scaleOrLength); } @Override @@ -282,13 +282,13 @@ public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throw @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { ensureOpen(); - values[parameterIndex-1] = encodeObject(x, sqlType2ClickHouseDataType(targetSqlType), scaleOrLength); + values[parameterIndex-1] = encodeObject(x, sqlType2ClickHouseDataType(targetSqlType), (long) scaleOrLength); } @Override public void setObject(int parameterIndex, Object x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, null, null); } @Override @@ -387,19 +387,19 @@ public void setRef(int parameterIndex, Ref x) throws SQLException { @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setArray(int parameterIndex, Array x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override @@ -469,7 +469,7 @@ public static String replaceQuestionMarks(String sql, final String replacement) @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(sqlDateToInstant(x, cal)); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Date, null); } protected Instant sqlDateToInstant(Date x, Calendar cal) { @@ -483,7 +483,7 @@ protected Instant sqlDateToInstant(Date x, Calendar cal) { @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(sqlTimeToInstant(x, cal)); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.Time, null); } protected Instant sqlTimeToInstant(Time x, Calendar cal) { @@ -497,7 +497,7 @@ protected Instant sqlTimeToInstant(Time x, Calendar cal) { @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(sqlTimestampToZDT(x, cal)); + values[parameterIndex - 1] = encodeObject(sqlTimestampToZDT(x, cal), ClickHouseDataType.DateTime64, 9L); } protected ZonedDateTime sqlTimestampToZDT(Timestamp x, Calendar cal) { @@ -511,13 +511,13 @@ protected ZonedDateTime sqlTimestampToZDT(Timestamp x, Calendar cal) { @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(null); + values[parameterIndex - 1] = encodeObject(null, sqlType2ClickHouseDataType(JDBCType.valueOf(sqlType)), null); } @Override public void setURL(int parameterIndex, URL x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } /** @@ -544,103 +544,103 @@ public void setRowId(int parameterIndex, RowId x) throws SQLException { @Override public void setNString(int parameterIndex, String x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setNCharacterStream(int parameterIndex, Reader x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x, length); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setNClob(int parameterIndex, NClob x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setClob(int parameterIndex, Reader x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setBlob(int parameterIndex, InputStream x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setNClob(int parameterIndex, Reader x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setSQLXML(int parameterIndex, SQLXML x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x, length); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x, length); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setCharacterStream(int parameterIndex, Reader x, long length) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x, length); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, length); } @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setCharacterStream(int parameterIndex, Reader x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setNCharacterStream(int parameterIndex, Reader x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setClob(int parameterIndex, Reader x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setBlob(int parameterIndex, InputStream x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override public void setNClob(int parameterIndex, Reader x) throws SQLException { ensureOpen(); - values[parameterIndex - 1] = encodeObject(x); + values[parameterIndex - 1] = encodeObject(x, ClickHouseDataType.String, null); } @Override @@ -759,8 +759,8 @@ public final int executeUpdate(String sql, String[] columnNames) throws SQLExcep "executeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!", ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE); } - private String encodeObject(Object x) throws SQLException { - return encodeObject(x, null); + private String encodeObject(Object x, ClickHouseDataType clickHouseDataType) throws SQLException { + return encodeObject(x, clickHouseDataType, (Long)null); } private static final char QUOTE = '\''; @@ -768,36 +768,49 @@ private String encodeObject(Object x) throws SQLException { private static final char O_BRACKET = '['; private static final char C_BRACKET = ']'; - private String encodeObject(Object x, Long length) throws SQLException { + + private String encodeObject(Object x, ClickHouseDataType clickHouseDataType, Long scaleOrLength) throws SQLException { LOG.trace("Encoding object: {}", x); + String typeCast = clickHouseDataType == null || clickHouseDataType == ClickHouseDataType.String ? "" : "::" + clickHouseDataType.getName(); + if (clickHouseDataType != null && clickHouseDataType.hasParameter()) { + if (scaleOrLength == null && (!clickHouseDataType.isNested())) { + throw new SQLException("Target type " + clickHouseDataType + " requires a parameter"); + } + typeCast += "(" + scaleOrLength + ")"; + } try { if (x == null) { return "NULL"; } else if (x instanceof String) { - return QUOTE + SQLUtils.escapeSingleQuotes((String) x) + QUOTE; + return QUOTE + SQLUtils.escapeSingleQuotes((String) x) + QUOTE + typeCast; + } else if (x instanceof BigDecimal) { + return x.toString(); + } else if (x instanceof Number) { + return x.toString() + typeCast; } else if (x instanceof Boolean) { - return (Boolean) x ? "1" : "0"; + return (Boolean) x ? "1" : "0" + typeCast; } else if (x instanceof Date) { - return QUOTE + DataTypeUtils.DATE_FORMATTER.format(((Date) x).toLocalDate()) + QUOTE; + return QUOTE + DataTypeUtils.DATE_FORMATTER.format(((Date) x).toLocalDate()) + QUOTE + "::Date32"; } else if (x instanceof LocalDate) { - return QUOTE + DataTypeUtils.DATE_FORMATTER.format((LocalDate) x) + QUOTE; + return QUOTE + DataTypeUtils.DATE_FORMATTER.format((LocalDate) x) + QUOTE + "::Date32"; } else if (x instanceof Time) { - return QUOTE + TIME_FORMATTER.format(((Time) x).toLocalTime()) + QUOTE; + return QUOTE + TIME_FORMATTER.format(((Time) x).toLocalTime()) + QUOTE + "::Time"; } else if (x instanceof LocalTime) { - return QUOTE + TIME_FORMATTER.format((LocalTime) x) + QUOTE; + return QUOTE + TIME_FORMATTER.format((LocalTime) x) + QUOTE + "::Time"; } else if (x instanceof Timestamp) { - return QUOTE + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + QUOTE; + return QUOTE + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + QUOTE + "::DateTime64(9)"; } else if (x instanceof LocalDateTime) { - return QUOTE + DATETIME_FORMATTER.format((LocalDateTime) x) + QUOTE; + return QUOTE + DATETIME_FORMATTER.format((LocalDateTime) x) + QUOTE + "::DateTime64(9)"; } else if (x instanceof OffsetDateTime) { - return encodeObject(((OffsetDateTime) x).toInstant()); + return encodeObject(((OffsetDateTime) x).toInstant(), ClickHouseDataType.DateTime64, 9L) + "::DateTime64"; } else if (x instanceof ZonedDateTime) { - return encodeObject(((ZonedDateTime) x).toInstant()); + ZonedDateTime zdt = (ZonedDateTime) x; + return QUOTE + DATETIME_FORMATTER.format(zdt) + QUOTE + "::DateTime64(9, " + zdt.getZone().getId() + ")"; } else if (x instanceof Instant) { return "fromUnixTimestamp64Nano(" + (((Instant) x).getEpochSecond() * 1_000_000_000L + ((Instant) x).getNano()) + ")"; } else if (x instanceof InetAddress) { - return QUOTE + ((InetAddress) x).getHostAddress() + QUOTE; + return QUOTE + ((InetAddress) x).getHostAddress() + QUOTE + typeCast; } else if (x instanceof java.sql.Array) { com.clickhouse.jdbc.types.Array array = (com.clickhouse.jdbc.types.Array) x; int nestedLevel = Math.max(1, array.getNestedLevel()); @@ -814,7 +827,7 @@ private String encodeObject(Object x, Long length) throws SQLException { if (x.getClass().getComponentType().isPrimitive()) { int len = java.lang.reflect.Array.getLength(x); for (int i = 0; i < len; i++) { - listString.append(encodeObject(java.lang.reflect.Array.get(x, i))).append(','); + listString.append(encodeObject(java.lang.reflect.Array.get(x, i), null, null)).append(','); } if (len > 0) { listString.setLength(listString.length() - 1); @@ -830,7 +843,7 @@ private String encodeObject(Object x, Long length) throws SQLException { listString.append(O_BRACKET); Collection collection = (Collection) x; for (Object item : collection) { - listString.append(encodeObject(item)).append(','); + listString.append(encodeObject(item, null, null)).append(','); } if (!collection.isEmpty()) { listString.setLength(listString.length() - 1); @@ -843,7 +856,8 @@ private String encodeObject(Object x, Long length) throws SQLException { StringBuilder mapString = new StringBuilder(); mapString.append('{'); for (Object key : tmpMap.keySet()) { - mapString.append(encodeObject(key)).append(": ").append(encodeObject(tmpMap.get(key))).append(','); + mapString.append(encodeObject(key, null, null)). + append(": ").append(encodeObject(tmpMap.get(key), null, null)).append(','); } if (!tmpMap.isEmpty()) { mapString.setLength(mapString.length() - 1); @@ -853,9 +867,9 @@ private String encodeObject(Object x, Long length) throws SQLException { return mapString.toString(); } else if (x instanceof Reader) { - return encodeCharacterStream((Reader) x, length); + return encodeCharacterStream((Reader) x, scaleOrLength); } else if (x instanceof InputStream) { - return encodeCharacterStream((InputStream) x, length); + return encodeCharacterStream((InputStream) x, scaleOrLength); } else if (x instanceof Tuple) { return encodeTuple(((Tuple)x).getValues()); } else if (x instanceof Struct) { @@ -883,7 +897,7 @@ private void appendArrayElements(Object[] array, StringBuilder sb, ClickHouseDat if (elementType == ClickHouseDataType.Tuple && item != null && item.getClass().isArray()) { sb.append(encodeTuple((Object[]) item)); } else { - sb.append(encodeObject(item)).append(','); + sb.append(encodeObject(item, null, null)).append(','); } } if (array.length > 0) { @@ -934,7 +948,7 @@ public String encodeArray(Object[] elements, int levels, ClickHouseDataType elem } else if (cursor.level == 1 && elementType == ClickHouseDataType.Tuple && element instanceof Object[] ) { cursor.arrayAsTuple = true; } else if (cursor.level == 1) { - arraySb.append(encodeObject(element)).append(','); + arraySb.append(encodeObject(element, null, null)).append(','); cursor.pos++; } else { cursor.pos++; @@ -1027,17 +1041,17 @@ private ClickHouseDataType sqlType2ClickHouseDataType(SQLType type) throws SQLEx return clickHouseDataType; } - private String encodeObject(Object x, ClickHouseDataType clickHouseDataType, Integer scaleOrLength) throws SQLException { - String encodedObject = encodeObject(x); - if (clickHouseDataType != null) { - encodedObject += "::" + clickHouseDataType.name(); - if (clickHouseDataType.hasParameter()) { - if (scaleOrLength == null) { - throw new SQLException("Target type " + clickHouseDataType + " requires a parameter"); - } - encodedObject += "(" + scaleOrLength + ")"; - } - } - return encodedObject; - } +// private String encodeObject(Object x, ClickHouseDataType clickHouseDataType, Integer scaleOrLength) throws SQLException { +// String encodedObject = encodeObject(x); +// if (clickHouseDataType != null) { +// encodedObject += "::" + clickHouseDataType.name(); +// if (clickHouseDataType.hasParameter()) { +// if (scaleOrLength == null) { +// throw new SQLException("Target type " + clickHouseDataType + " requires a parameter"); +// } +// encodedObject += "(" + scaleOrLength + ")"; +// } +// } +// return encodedObject; +// } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java index a42429548..9c2f55cc8 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java @@ -781,7 +781,8 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam " JOIN system.databases d ON system.tables.database = system.databases.name" + " WHERE t.database LIKE '" + (schemaPattern == null ? "%" : schemaPattern) + "'" + " AND t.name LIKE '" + (tableNamePattern == null ? "%" : tableNamePattern) + "'" + - " AND TABLE_TYPE IN ('" + String.join("','", types) + "')"; + " AND TABLE_TYPE IN ('" + String.join("','", types) + "') " + + " ORDER BY TABLE_SCHEM, TABLE_TYPE, TABLE_NAME"; try { return connection.createStatement().executeQuery(sql); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java index bb958adb4..6d1cc98a4 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -28,7 +28,9 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Time; import java.sql.Timestamp; +import java.sql.Types; import java.text.DecimalFormat; import java.time.Instant; import java.time.LocalDate; @@ -1651,4 +1653,75 @@ public void testVariantTypesSimpleStatement() throws SQLException { } } } + + @Test(groups = { "integration" }) + public void testDateWithExpressions() throws Exception { + Properties props = new Properties(); + props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1"); + try (Connection conn = getJdbcConnection(props)) { + try (PreparedStatement stmt = conn.prepareStatement("SELECT toMonth(?) as d1, toYear(?) as d2, toTime64(?, 3) as t1, toTime64(?, 3) as t2")) { + Date date = Date.valueOf("2024-12-01"); + Time time = Time.valueOf("16:30:00"); + stmt.setDate(1, date); + stmt.setObject(2, date); + stmt.setTime(3, time); + stmt.setObject(4, time); + + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(rs.getString(1), "12"); + assertEquals(rs.getString(2), "2024"); + assertEquals(rs.getTime(3), "16"); + assertEquals(rs.getTime(4), "30"); + } + } + + final String testTable = "test_date_expressions"; + try (Statement stmt = conn.createStatement()) { + stmt.execute("DROP TABLE IF EXISTS " + testTable); + stmt.execute("CREATE TABLE " + testTable + " (d Date, event String) ENGINE = MergeTree() ORDER BY ()"); + stmt.execute("INSERT INTO " + testTable + " VALUES ('2024-12-01', 'event1'), ('2024-12-02', 'event2'), ('2024-12-03', 'event3')"); + } + + try (PreparedStatement stmt = conn.prepareStatement("SELECT d, event FROM " + testTable + " WHERE d IN(?) ORDER BY event")){ + Date[] dates = new Date[] { Date.valueOf("2024-12-01"), Date.valueOf("2024-12-03") }; + stmt.setArray(1, conn.createArrayOf("Date", dates)); + try (ResultSet rs = stmt.executeQuery()) { + String[] events = new String[] { "event1", "event3" }; + String[] datesStr = new String[] { "2024-12-01", "2024-12-03" }; + + for (int i = 0; i < events.length; i++) { + assertTrue(rs.next()); + assertEquals(rs.getString("event"), events[i]); + assertEquals(rs.getString("d"), datesStr[i]); + } + } + } + + final String dateTimeTable = "test_date_time_expressions"; + try (Statement stmt = conn.createStatement()) { + stmt.execute("DROP TABLE IF EXISTS " + dateTimeTable); + stmt.execute("CREATE TABLE " + dateTimeTable + " (t DateTime32, event String) ENGINE = MergeTree() ORDER BY ()"); + stmt.execute("INSERT INTO " + dateTimeTable + " VALUES ('2024-12-01 00:10:00', 'event1'), ('2024-12-02 00:20:00', 'event2'), " + + "('2024-12-03 00:30:00', 'event3')"); + } + try (PreparedStatement stmt = conn.prepareStatement("SELECT t, event FROM " + dateTimeTable + " WHERE t IN(?) ORDER BY event")){ + Timestamp[] timestamps = new Timestamp[] { + Timestamp.valueOf("2024-12-01 00:10:00"), + Timestamp.valueOf("2024-12-03 00:30:00") + }; + stmt.setArray(1, conn.createArrayOf("DateTime32", timestamps)); + try (ResultSet rs = stmt.executeQuery()) { + String[] events = new String[] { "event1", "event3" }; + String[] timestampsStr = new String[] { "2024-12-01 00:10:00", "2024-12-03 00:30:00" }; + + for (int i = 0; i < events.length; i++) { + assertTrue(rs.next()); + assertEquals(rs.getString("event"), events[i]); + assertEquals(rs.getString("t"), timestampsStr[i]); + } + } + } + } + } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java index b3cda1247..997839092 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java @@ -1,5 +1,6 @@ package com.clickhouse.jdbc; +import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.ClickHouseVersion; @@ -218,7 +219,9 @@ public void testSetDate() throws Exception { @Test(groups = { "integration" }) public void testSetTime() throws Exception { - try (Connection conn = getJdbcConnection()) { + Properties props = new Properties(); + props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1"); + try (Connection conn = getJdbcConnection(props)) { try (PreparedStatement stmt = conn.prepareStatement("SELECT toDateTime(?)")) { stmt.setTime(1, java.sql.Time.valueOf("12:34:56"), new GregorianCalendar(TimeZone.getTimeZone("UTC"))); try (ResultSet rs = stmt.executeQuery()) {