From 48c153ecfdc816686f8ad6f59ddd57cda19c78d5 Mon Sep 17 00:00:00 2001 From: Paultagoras Date: Fri, 20 Dec 2024 12:07:45 -0500 Subject: [PATCH 1/5] Cleanup + Type Conversion --- .../com/clickhouse/jdbc/ConnectionImpl.java | 2 +- .../com/clickhouse/jdbc/ResultSetImpl.java | 310 ++++-------------- .../clickhouse/jdbc/internal/JdbcUtils.java | 118 +++++-- .../jdbc/metadata/ParameterMetaData.java | 2 +- .../jdbc/metadata/ResultSetMetaData.java | 2 +- .../com/clickhouse/jdbc/DataTypeTests.java | 69 ++++ 6 files changed, 223 insertions(+), 280 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 1d6af2835..028960c32 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -484,7 +484,7 @@ public Properties getClientInfo() throws SQLException { @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { try { - return new com.clickhouse.jdbc.types.Array(List.of(elements), typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName))); + return new com.clickhouse.jdbc.types.Array(List.of(elements), typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName)).getVendorTypeNumber()); } catch (Exception e) { throw new SQLException("Failed to create array", ExceptionUtils.SQL_STATE_CLIENT_ERROR, e); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index b71f4fa62..a33dc1b59 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -603,18 +603,7 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public Object getObject(int columnIndex) throws SQLException { - checkClosed(); - try { - if (reader.hasValue(columnIndex)) { - wasNull = false; - return reader.readValue(columnIndex); - } else { - wasNull = true; - return null; - } - } catch (Exception e) { - throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getObject(%s)", parentStatement.getLastSql(), columnIndex), e); - } + return getObject(getSchema().columnIndexToName(columnIndex)); } @Override @@ -876,154 +865,97 @@ public boolean rowDeleted() throws SQLException { @Override public void updateNull(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNull(getSchema().columnIndexToName(columnIndex)); } @Override public void updateBoolean(int columnIndex, boolean x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBoolean(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateByte(int columnIndex, byte x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateByte(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateShort(int columnIndex, short x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateShort(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateInt(int columnIndex, int x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateInt(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateLong(int columnIndex, long x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateLong(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateFloat(int columnIndex, float x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateFloat(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateDouble(int columnIndex, double x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateDouble(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBigDecimal(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateString(int columnIndex, String x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateString(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateBytes(int columnIndex, byte[] x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBytes(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateDate(int columnIndex, Date x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateDate(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateTime(int columnIndex, Time x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateTime(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateTimestamp(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateAsciiStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBinaryStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateCharacterStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateObject(getSchema().columnIndexToName(columnIndex), x, scaleOrLength); } @Override public void updateObject(int columnIndex, Object x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateObject(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1221,50 +1153,38 @@ public Statement getStatement() throws SQLException { @Override public Object getObject(int columnIndex, Map> map) throws SQLException { - checkClosed(); - return getObject(columnIndex); + return getObject(getSchema().columnIndexToName(columnIndex), map); } @Override public Ref getRef(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Ref is not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } - - return null; + return getRef(getSchema().columnIndexToName(columnIndex)); } @Override public Blob getBlob(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Blob is not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } - - return null; + return getBlob(getSchema().columnIndexToName(columnIndex)); } @Override public java.sql.Clob getClob(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Clob is not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } - - return null; + return getClob(getSchema().columnIndexToName(columnIndex)); } @Override public java.sql.Array getArray(int columnIndex) throws SQLException { - checkClosed(); - return getArray(reader.getSchema().columnIndexToName(columnIndex)); + return getArray(getSchema().columnIndexToName(columnIndex)); } @Override public Object getObject(String columnLabel, Map> map) throws SQLException { checkClosed(); - return getObject(columnLabel); + log.debug("getObject(columnLabel={}, map={})", columnLabel, map); + if (map == null) { + return getObject(columnLabel); + } + + return getObject(columnLabel, map.get(JdbcUtils.convertToSqlType(getSchema().getColumnByName(columnLabel).getDataType()).getName())); } @Override @@ -1301,10 +1221,10 @@ public Clob getClob(String columnLabel) throws SQLException { public java.sql.Array getArray(String columnLabel) throws SQLException { checkClosed(); try { - ClickHouseColumn column = reader.getSchema().getColumnByName(columnLabel); + ClickHouseColumn column = getSchema().getColumnByName(columnLabel); return new Array(reader.getList(columnLabel), column.getArrayBaseColumn().getDataType().name(), - JdbcUtils.convertToSqlType(column.getArrayBaseColumn().getDataType())); + JdbcUtils.convertToSqlType(column.getArrayBaseColumn().getDataType()).getVendorTypeNumber()); } catch (Exception e) { throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getArray(%s)", parentStatement.getLastSql(), columnLabel), e); } @@ -1312,8 +1232,7 @@ public java.sql.Array getArray(String columnLabel) throws SQLException { @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { - checkClosed(); - return getDate(columnIndex); + return getDate(getSchema().columnIndexToName(columnIndex), cal); } @Override @@ -1324,8 +1243,7 @@ public Date getDate(String columnLabel, Calendar cal) throws SQLException { @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { - checkClosed(); - return getTime(columnIndex); + return getTime(getSchema().columnIndexToName(columnIndex), cal); } @Override @@ -1336,8 +1254,7 @@ public Time getTime(String columnLabel, Calendar cal) throws SQLException { @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { - checkClosed(); - return getTimestamp(columnIndex); + return getTimestamp(getSchema().columnIndexToName(columnIndex), cal); } @Override @@ -1348,12 +1265,7 @@ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLExcept @Override public URL getURL(int columnIndex) throws SQLException { - checkClosed(); - try { - return new URL(reader.getString(columnIndex)); - } catch (MalformedURLException e) { - throw new SQLDataException(e); - } + return getURL(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1368,10 +1280,7 @@ public URL getURL(String columnLabel) throws SQLException { @Override public void updateRef(int columnIndex, Ref x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateRef(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1384,10 +1293,7 @@ public void updateRef(String columnLabel, Ref x) throws SQLException { @Override public void updateBlob(int columnIndex, Blob x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBlob(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1400,10 +1306,7 @@ public void updateBlob(String columnLabel, Blob x) throws SQLException { @Override public void updateClob(int columnIndex, Clob x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateClob(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1416,10 +1319,7 @@ public void updateClob(String columnLabel, Clob x) throws SQLException { @Override public void updateArray(int columnIndex, java.sql.Array x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateArray(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1432,8 +1332,7 @@ public void updateArray(String columnLabel, java.sql.Array x) throws SQLExceptio @Override public RowId getRowId(int columnIndex) throws SQLException { - checkClosed(); - return null; + return getRowId(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1444,10 +1343,7 @@ public RowId getRowId(String columnLabel) throws SQLException { @Override public void updateRowId(int columnIndex, RowId x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateRowId(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1471,10 +1367,7 @@ public boolean isClosed() throws SQLException { @Override public void updateNString(int columnIndex, String nString) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNString(getSchema().columnIndexToName(columnIndex), nString); } @Override @@ -1487,10 +1380,7 @@ public void updateNString(String columnLabel, String nString) throws SQLExceptio @Override public void updateNClob(int columnIndex, NClob nClob) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNClob(getSchema().columnIndexToName(columnIndex), nClob); } @Override @@ -1503,12 +1393,7 @@ public void updateNClob(String columnLabel, NClob nClob) throws SQLException { @Override public NClob getNClob(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("NClob is not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } - - return null; + return getNClob(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1523,12 +1408,7 @@ public NClob getNClob(String columnLabel) throws SQLException { @Override public SQLXML getSQLXML(int columnIndex) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("SQLXML is not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } - - return null; + return getSQLXML(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1543,10 +1423,7 @@ public SQLXML getSQLXML(String columnLabel) throws SQLException { @Override public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateSQLXML(getSchema().columnIndexToName(columnIndex), xmlObject); } @Override @@ -1559,8 +1436,7 @@ public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLExcepti @Override public String getNString(int columnIndex) throws SQLException { - checkClosed(); - return reader.getString(columnIndex); + return getNString(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1571,8 +1447,7 @@ public String getNString(String columnLabel) throws SQLException { @Override public Reader getNCharacterStream(int columnIndex) throws SQLException { - checkClosed(); - return new CharArrayReader(reader.getString(columnIndex).toCharArray()); + return getNCharacterStream(getSchema().columnIndexToName(columnIndex)); } @Override @@ -1583,10 +1458,7 @@ public Reader getNCharacterStream(String columnLabel) throws SQLException { @Override public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNCharacterStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override @@ -1599,26 +1471,17 @@ public void updateNCharacterStream(String columnLabel, Reader reader, long lengt @Override public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateAsciiStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBinaryStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateCharacterStream(getSchema().columnIndexToName(columnIndex), x, length); } @Override @@ -1647,10 +1510,7 @@ public void updateCharacterStream(String columnLabel, Reader reader, long length @Override public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBlob(getSchema().columnIndexToName(columnIndex), inputStream, length); } @Override @@ -1663,10 +1523,7 @@ public void updateBlob(String columnLabel, InputStream inputStream, long length) @Override public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateClob(getSchema().columnIndexToName(columnIndex), reader, length); } @Override @@ -1679,10 +1536,7 @@ public void updateClob(String columnLabel, Reader reader, long length) throws SQ @Override public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNClob(getSchema().columnIndexToName(columnIndex), reader, length); } @Override @@ -1695,10 +1549,7 @@ public void updateNClob(String columnLabel, Reader reader, long length) throws S @Override public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNCharacterStream(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1711,26 +1562,17 @@ public void updateNCharacterStream(String columnLabel, Reader reader) throws SQL @Override public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateAsciiStream(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBinaryStream(getSchema().columnIndexToName(columnIndex), x); } @Override public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateCharacterStream(getSchema().columnIndexToName(columnIndex), x); } @Override @@ -1759,10 +1601,7 @@ public void updateCharacterStream(String columnLabel, Reader reader) throws SQLE @Override public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateBlob(getSchema().columnIndexToName(columnIndex), inputStream); } @Override @@ -1775,10 +1614,7 @@ public void updateBlob(String columnLabel, InputStream inputStream) throws SQLEx @Override public void updateClob(int columnIndex, Reader reader) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateClob(getSchema().columnIndexToName(columnIndex), reader); } @Override @@ -1791,10 +1627,7 @@ public void updateClob(String columnLabel, Reader reader) throws SQLException { @Override public void updateNClob(int columnIndex, Reader reader) throws SQLException { - checkClosed(); - if (!parentStatement.connection.config.isIgnoreUnsupportedRequests()) { - throw new SQLFeatureNotSupportedException("Writes are not supported.", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); - } + updateNClob(getSchema().columnIndexToName(columnIndex), reader); } @Override @@ -1807,19 +1640,14 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { @Override public T getObject(int columnIndex, Class type) throws SQLException { - checkClosed(); - try { - return (T) reader.readValue(columnIndex); - } catch (Exception e) { - throw ExceptionUtils.toSqlState(e); - } + return getObject(getSchema().columnIndexToName(columnIndex), type); } @Override public T getObject(String columnLabel, Class type) throws SQLException { checkClosed(); try { - return (T) reader.readValue(columnLabel); + return (T) JdbcUtils.convert(getObject(columnLabel), type); } catch (Exception e) { throw ExceptionUtils.toSqlState(e); } @@ -1827,8 +1655,7 @@ public T getObject(String columnLabel, Class type) throws SQLException { @Override public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { - checkClosed(); - ResultSet.super.updateObject(columnIndex, x, targetSqlType, scaleOrLength); + updateObject(getSchema().columnIndexToName(columnIndex), x, targetSqlType, scaleOrLength); } @Override @@ -1839,8 +1666,7 @@ public void updateObject(String columnLabel, Object x, SQLType targetSqlType, in @Override public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException { - checkClosed(); - ResultSet.super.updateObject(columnIndex, x, targetSqlType); + updateObject(getSchema().columnIndexToName(columnIndex), x, targetSqlType); } @Override diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index 83ec32abc..3da84a0e2 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -2,7 +2,14 @@ import com.clickhouse.data.ClickHouseDataType; -import java.sql.Types; +import java.sql.JDBCType; +import java.sql.SQLException; +import java.sql.SQLType; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -12,52 +19,53 @@ public class JdbcUtils { //Define a map to store the mapping between ClickHouse data types and SQL data types - private static final Map CLICKHOUSE_TO_SQL_TYPE_MAP = generateTypeMap(); - private static Map generateTypeMap() { - Map map = new TreeMap<>(); // TreeMap is used to sort the keys in natural order so FixedString will be before String :-) (type match should be more accurate) - map.put(ClickHouseDataType.Int8, Types.TINYINT); - map.put(ClickHouseDataType.UInt8, Types.TINYINT); - map.put(ClickHouseDataType.Int16, Types.SMALLINT); - map.put(ClickHouseDataType.UInt16, Types.SMALLINT); - map.put(ClickHouseDataType.Int32, Types.INTEGER); - map.put(ClickHouseDataType.UInt32, Types.INTEGER); - map.put(ClickHouseDataType.Int64, Types.BIGINT); - map.put(ClickHouseDataType.UInt64, Types.BIGINT); - map.put(ClickHouseDataType.Float32, Types.FLOAT); - map.put(ClickHouseDataType.Float64, Types.DOUBLE); - map.put(ClickHouseDataType.Decimal, Types.DECIMAL); - map.put(ClickHouseDataType.Decimal32, Types.DECIMAL); - map.put(ClickHouseDataType.Decimal64, Types.DECIMAL); - map.put(ClickHouseDataType.Decimal128, Types.DECIMAL); - map.put(ClickHouseDataType.String, Types.VARCHAR); - map.put(ClickHouseDataType.FixedString, Types.CHAR); - map.put(ClickHouseDataType.Enum8, Types.VARCHAR); - map.put(ClickHouseDataType.Enum16, Types.VARCHAR); - map.put(ClickHouseDataType.Date, Types.DATE); - map.put(ClickHouseDataType.Date32, Types.DATE); - map.put(ClickHouseDataType.DateTime, Types.TIMESTAMP_WITH_TIMEZONE); - map.put(ClickHouseDataType.DateTime32, Types.TIMESTAMP_WITH_TIMEZONE); - map.put(ClickHouseDataType.DateTime64, Types.TIMESTAMP_WITH_TIMEZONE); - map.put(ClickHouseDataType.Array, Types.ARRAY); - map.put(ClickHouseDataType.Nested, Types.ARRAY); - map.put(ClickHouseDataType.Map, Types.JAVA_OBJECT); + private static final Map CLICKHOUSE_TO_SQL_TYPE_MAP = generateTypeMap(); + private static Map generateTypeMap() { + Map map = new TreeMap<>(); // TreeMap is used to sort the keys in natural order so FixedString will be before String :-) (type match should be more accurate) + map.put(ClickHouseDataType.Int8, JDBCType.TINYINT); + map.put(ClickHouseDataType.UInt8, JDBCType.TINYINT); + map.put(ClickHouseDataType.Int16, JDBCType.SMALLINT); + map.put(ClickHouseDataType.UInt16, JDBCType.SMALLINT); + map.put(ClickHouseDataType.Int32, JDBCType.INTEGER); + map.put(ClickHouseDataType.UInt32, JDBCType.INTEGER); + map.put(ClickHouseDataType.Int64, JDBCType.BIGINT); + map.put(ClickHouseDataType.UInt64, JDBCType.BIGINT); + map.put(ClickHouseDataType.Float32, JDBCType.FLOAT); + map.put(ClickHouseDataType.Float64, JDBCType.DOUBLE); + map.put(ClickHouseDataType.Decimal, JDBCType.DECIMAL); + map.put(ClickHouseDataType.Decimal32, JDBCType.DECIMAL); + map.put(ClickHouseDataType.Decimal64, JDBCType.DECIMAL); + map.put(ClickHouseDataType.Decimal128, JDBCType.DECIMAL); + map.put(ClickHouseDataType.String, JDBCType.VARCHAR); + map.put(ClickHouseDataType.FixedString, JDBCType.CHAR); + map.put(ClickHouseDataType.Enum8, JDBCType.VARCHAR); + map.put(ClickHouseDataType.Enum16, JDBCType.VARCHAR); + map.put(ClickHouseDataType.Date, JDBCType.DATE); + map.put(ClickHouseDataType.Date32, JDBCType.DATE); + map.put(ClickHouseDataType.DateTime, JDBCType.TIMESTAMP_WITH_TIMEZONE); + map.put(ClickHouseDataType.DateTime32, JDBCType.TIMESTAMP_WITH_TIMEZONE); + map.put(ClickHouseDataType.DateTime64, JDBCType.TIMESTAMP_WITH_TIMEZONE); + map.put(ClickHouseDataType.Array, JDBCType.ARRAY); + map.put(ClickHouseDataType.Nested, JDBCType.ARRAY); + map.put(ClickHouseDataType.Map, JDBCType.JAVA_OBJECT); return map; } - public static int convertToSqlType(ClickHouseDataType clickhouseType) { + public static SQLType convertToSqlType(ClickHouseDataType clickhouseType) { if (clickhouseType == null) { - return Types.NULL; + return JDBCType.NULL; } - return CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(clickhouseType, Types.OTHER); + return CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(clickhouseType, JDBCType.OTHER); } + public static String generateSqlTypeEnum(String columnName) { StringBuilder sql = new StringBuilder("multiIf("); for (ClickHouseDataType type : CLICKHOUSE_TO_SQL_TYPE_MAP.keySet()) { - sql.append("position(").append(columnName).append(", '").append(type.name()).append("') > 0, ").append(CLICKHOUSE_TO_SQL_TYPE_MAP.get(type)).append(", "); + sql.append("position(").append(columnName).append(", '").append(type.name()).append("') > 0, ").append(CLICKHOUSE_TO_SQL_TYPE_MAP.get(type).getVendorTypeNumber()).append(", "); } - sql.append(Types.OTHER + ")"); + sql.append(JDBCType.OTHER.getVendorTypeNumber()).append(")"); return sql.toString(); } @@ -123,4 +131,44 @@ public static String generateSqlTypeSizes(String columnName) { return sql.toString(); } + + public static Object convert(Object value, Class type) throws SQLException { + if (value == null || type == null) { + return value; + } + try { + if (type.isInstance(value)) { + return value; + } else if (type == String.class) { + return value.toString(); + } else if (type == Boolean.class || type == boolean.class) { + return Boolean.parseBoolean(value.toString()); + } else if (type == Byte.class || type == byte.class) { + return Byte.parseByte(value.toString()); + } else if (type == Short.class || type == short.class) { + return Short.parseShort(value.toString()); + } else if (type == Integer.class || type == int.class) { + return Integer.parseInt(value.toString()); + } else if (type == Long.class || type == long.class) { + return Long.parseLong(value.toString()); + } else if (type == Float.class || type == float.class) { + return Float.parseFloat(value.toString()); + } else if (type == Double.class || type == double.class) { + return Double.parseDouble(value.toString()); + } else if (type == LocalDate.class && value instanceof TemporalAccessor) { + return LocalDate.from((TemporalAccessor) value); + } else if (type == LocalDateTime.class && value instanceof TemporalAccessor) { + return LocalDateTime.from((TemporalAccessor) value); + } else if (type == OffsetDateTime.class && value instanceof TemporalAccessor) { + return OffsetDateTime.from((TemporalAccessor) value); + } else if (type == ZonedDateTime.class && value instanceof TemporalAccessor) { + return ZonedDateTime.from((TemporalAccessor) value); + } + } catch (Exception e) { + throw new SQLException("Failed to convert " + value + " to " + type.getName(), ExceptionUtils.SQL_STATE_DATA_EXCEPTION); + } + + throw new SQLException("Unsupported conversion from " + value.getClass().getName() + " to " + type.getName(), ExceptionUtils.SQL_STATE_DATA_EXCEPTION); + } + } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java index c91a17d42..912aaf501 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ParameterMetaData.java @@ -76,7 +76,7 @@ public int getScale(int param) throws SQLException { public int getParameterType(int param) throws SQLException { //TODO: Should we implement .getSQLType()? try { - return JdbcUtils.convertToSqlType(getParam(param).getDataType()); + return JdbcUtils.convertToSqlType(getParam(param).getDataType()).getVendorTypeNumber(); } catch (Exception e) { throw ExceptionUtils.toSqlState(e); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java index a0048c6dd..d5dea3347 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java @@ -133,7 +133,7 @@ public String getCatalogName(int column) throws SQLException { @Override public int getColumnType(int column) throws SQLException { try { - return JdbcUtils.convertToSqlType(getColumn(column).getDataType()); + return JdbcUtils.convertToSqlType(getColumn(column).getDataType()).getVendorTypeNumber(); } catch (Exception e) { throw ExceptionUtils.toSqlState(e); } 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 49120c11d..301254fe9 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -12,10 +12,19 @@ import java.sql.Connection; import java.sql.Date; import java.sql.DriverManager; +import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Random; @@ -846,4 +855,64 @@ public void testDynamicTypesSimpleStatement() throws SQLException { } } } + + + @Test(groups = { "integration" }) + public void testTypeConversions() throws Exception { + try (Connection conn = getConnection()) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT 1, 'true', '1.0', " + + "toDate('2024-12-01'), toDateTime('2024-12-01 12:34:56'), toDateTime64('2024-12-01 12:34:56.789', 3), toDateTime64('2024-12-01 12:34:56.789789', 6), toDateTime64('2024-12-01 12:34:56.789789789', 9)")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 1); + assertEquals(String.valueOf(rs.getObject(1)), "1"); + assertEquals(rs.getObject(1, Integer.class), 1); + assertEquals(rs.getObject(1, Long.class), 1L); + assertEquals(String.valueOf(rs.getObject(1, new HashMap<>(){{put(JDBCType.INTEGER.getName(), Integer.class);}})), "1"); + + assertTrue(rs.getBoolean(2)); + assertEquals(String.valueOf(rs.getObject(2)), "true"); + assertEquals(rs.getObject(2, Boolean.class), true); + assertEquals(String.valueOf(rs.getObject(2, new HashMap<>(){{put(JDBCType.BOOLEAN.getName(), Boolean.class);}})), "true"); + + assertEquals(rs.getFloat(3), 1.0f); + assertEquals(String.valueOf(rs.getObject(3)), "1.0"); + assertEquals(rs.getObject(3, Float.class), 1.0f); + assertEquals(rs.getObject(3, Double.class), 1.0); + assertEquals(String.valueOf(rs.getObject(3, new HashMap<>(){{put(JDBCType.FLOAT.getName(), Float.class);}})), "1.0"); + + assertEquals(rs.getDate(4), Date.valueOf("2024-12-01")); + assertTrue(rs.getObject(4) instanceof ZonedDateTime);//ZonedDateTime + assertEquals(String.valueOf(rs.getObject(4)), "2024-12-01T00:00Z[UTC]");//ZonedDateTime + assertEquals(rs.getObject(4, LocalDate.class), LocalDate.of(2024, 12, 1)); + assertEquals(String.valueOf(rs.getObject(4, new HashMap<>(){{put(JDBCType.DATE.getName(), LocalDate.class);}})), "2024-12-01"); + + assertEquals(rs.getTimestamp(5), Timestamp.valueOf("2024-12-01 12:34:56")); + assertTrue(rs.getObject(5) instanceof ZonedDateTime);//ZonedDateTime + assertEquals(String.valueOf(rs.getObject(5)), "2024-12-01T12:34:56Z[UTC]");//ZonedDateTime + assertEquals(rs.getObject(5, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56)); + assertEquals(String.valueOf(rs.getObject(5, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56"); + + assertEquals(rs.getTimestamp(6), Timestamp.valueOf("2024-12-01 12:34:56.789")); + assertTrue(rs.getObject(6) instanceof ZonedDateTime);//ZonedDateTime + assertEquals(String.valueOf(rs.getObject(6)), "2024-12-01T12:34:56.789Z[UTC]");//ZonedDateTime + assertEquals(rs.getObject(6, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789000000)); + assertEquals(String.valueOf(rs.getObject(6, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56.789"); + + assertEquals(rs.getTimestamp(7), Timestamp.valueOf("2024-12-01 12:34:56.789789")); + assertTrue(rs.getObject(7) instanceof ZonedDateTime);//ZonedDateTime + assertEquals(String.valueOf(rs.getObject(7)), "2024-12-01T12:34:56.789789Z[UTC]");//ZonedDateTime + assertEquals(rs.getObject(7, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789000)); + assertEquals(String.valueOf(rs.getObject(7, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), OffsetDateTime.class);}})), "2024-12-01T12:34:56.789789Z"); + + assertEquals(rs.getTimestamp(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789")); + assertTrue(rs.getObject(8) instanceof ZonedDateTime);//ZonedDateTime + assertEquals(String.valueOf(rs.getObject(8)), "2024-12-01T12:34:56.789789789Z[UTC]");//ZonedDateTime + assertEquals(rs.getObject(8, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789789)); + assertEquals(String.valueOf(rs.getObject(8, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), ZonedDateTime.class);}})), "2024-12-01T12:34:56.789789789Z[UTC]"); + + } + } + } + } } From 6d30ed9f1475c853d83a7f4e62501681adea2397 Mon Sep 17 00:00:00 2001 From: Paultagoras Date: Fri, 20 Dec 2024 16:47:37 -0500 Subject: [PATCH 2/5] Update DataTypeTests.java --- .../java/com/clickhouse/jdbc/DataTypeTests.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 301254fe9..2945480f3 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -868,48 +868,48 @@ public void testTypeConversions() throws Exception { assertEquals(String.valueOf(rs.getObject(1)), "1"); assertEquals(rs.getObject(1, Integer.class), 1); assertEquals(rs.getObject(1, Long.class), 1L); - assertEquals(String.valueOf(rs.getObject(1, new HashMap<>(){{put(JDBCType.INTEGER.getName(), Integer.class);}})), "1"); + assertEquals(String.valueOf(rs.getObject(1, new HashMap>(){{put(JDBCType.INTEGER.getName(), Integer.class);}})), "1"); assertTrue(rs.getBoolean(2)); assertEquals(String.valueOf(rs.getObject(2)), "true"); assertEquals(rs.getObject(2, Boolean.class), true); - assertEquals(String.valueOf(rs.getObject(2, new HashMap<>(){{put(JDBCType.BOOLEAN.getName(), Boolean.class);}})), "true"); + assertEquals(String.valueOf(rs.getObject(2, new HashMap>(){{put(JDBCType.BOOLEAN.getName(), Boolean.class);}})), "true"); assertEquals(rs.getFloat(3), 1.0f); assertEquals(String.valueOf(rs.getObject(3)), "1.0"); assertEquals(rs.getObject(3, Float.class), 1.0f); assertEquals(rs.getObject(3, Double.class), 1.0); - assertEquals(String.valueOf(rs.getObject(3, new HashMap<>(){{put(JDBCType.FLOAT.getName(), Float.class);}})), "1.0"); + assertEquals(String.valueOf(rs.getObject(3, new HashMap>(){{put(JDBCType.FLOAT.getName(), Float.class);}})), "1.0"); assertEquals(rs.getDate(4), Date.valueOf("2024-12-01")); assertTrue(rs.getObject(4) instanceof ZonedDateTime);//ZonedDateTime assertEquals(String.valueOf(rs.getObject(4)), "2024-12-01T00:00Z[UTC]");//ZonedDateTime assertEquals(rs.getObject(4, LocalDate.class), LocalDate.of(2024, 12, 1)); - assertEquals(String.valueOf(rs.getObject(4, new HashMap<>(){{put(JDBCType.DATE.getName(), LocalDate.class);}})), "2024-12-01"); + assertEquals(String.valueOf(rs.getObject(4, new HashMap>(){{put(JDBCType.DATE.getName(), LocalDate.class);}})), "2024-12-01"); assertEquals(rs.getTimestamp(5), Timestamp.valueOf("2024-12-01 12:34:56")); assertTrue(rs.getObject(5) instanceof ZonedDateTime);//ZonedDateTime assertEquals(String.valueOf(rs.getObject(5)), "2024-12-01T12:34:56Z[UTC]");//ZonedDateTime assertEquals(rs.getObject(5, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56)); - assertEquals(String.valueOf(rs.getObject(5, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56"); + assertEquals(String.valueOf(rs.getObject(5, new HashMap>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56"); assertEquals(rs.getTimestamp(6), Timestamp.valueOf("2024-12-01 12:34:56.789")); assertTrue(rs.getObject(6) instanceof ZonedDateTime);//ZonedDateTime assertEquals(String.valueOf(rs.getObject(6)), "2024-12-01T12:34:56.789Z[UTC]");//ZonedDateTime assertEquals(rs.getObject(6, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789000000)); - assertEquals(String.valueOf(rs.getObject(6, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56.789"); + assertEquals(String.valueOf(rs.getObject(6, new HashMap>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56.789"); assertEquals(rs.getTimestamp(7), Timestamp.valueOf("2024-12-01 12:34:56.789789")); assertTrue(rs.getObject(7) instanceof ZonedDateTime);//ZonedDateTime assertEquals(String.valueOf(rs.getObject(7)), "2024-12-01T12:34:56.789789Z[UTC]");//ZonedDateTime assertEquals(rs.getObject(7, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789000)); - assertEquals(String.valueOf(rs.getObject(7, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), OffsetDateTime.class);}})), "2024-12-01T12:34:56.789789Z"); + assertEquals(String.valueOf(rs.getObject(7, new HashMap>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), OffsetDateTime.class);}})), "2024-12-01T12:34:56.789789Z"); assertEquals(rs.getTimestamp(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789")); assertTrue(rs.getObject(8) instanceof ZonedDateTime);//ZonedDateTime assertEquals(String.valueOf(rs.getObject(8)), "2024-12-01T12:34:56.789789789Z[UTC]");//ZonedDateTime assertEquals(rs.getObject(8, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789789)); - assertEquals(String.valueOf(rs.getObject(8, new HashMap<>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), ZonedDateTime.class);}})), "2024-12-01T12:34:56.789789789Z[UTC]"); + assertEquals(String.valueOf(rs.getObject(8, new HashMap>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), ZonedDateTime.class);}})), "2024-12-01T12:34:56.789789789Z[UTC]"); } } From be52a871647026bb59ea1a71144bc5945a482d2d Mon Sep 17 00:00:00 2001 From: Paultagoras Date: Sat, 21 Dec 2024 20:13:07 -0500 Subject: [PATCH 3/5] Update JdbcUtils.java --- .../main/java/com/clickhouse/jdbc/internal/JdbcUtils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index 3da84a0e2..d4c0737dd 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -2,11 +2,13 @@ import com.clickhouse.data.ClickHouseDataType; +import java.sql.Date; import java.sql.JDBCType; import java.sql.SQLException; import java.sql.SQLType; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; @@ -163,6 +165,12 @@ public static Object convert(Object value, Class type) throws SQLException { return OffsetDateTime.from((TemporalAccessor) value); } else if (type == ZonedDateTime.class && value instanceof TemporalAccessor) { return ZonedDateTime.from((TemporalAccessor) value); + } else if (type == Date.class && value instanceof TemporalAccessor) { + return Date.valueOf(LocalDate.from((TemporalAccessor) value)); + } else if (type == java.sql.Timestamp.class && value instanceof TemporalAccessor) { + return java.sql.Timestamp.valueOf(LocalDateTime.from((TemporalAccessor) value)); + } else if (type == java.sql.Time.class && value instanceof TemporalAccessor) { + return java.sql.Time.valueOf(LocalTime.from((TemporalAccessor) value)); } } catch (Exception e) { throw new SQLException("Failed to convert " + value + " to " + type.getName(), ExceptionUtils.SQL_STATE_DATA_EXCEPTION); From c2fa5d8b184c1b3c26d951ccee0d26f34f6be064 Mon Sep 17 00:00:00 2001 From: Paultagoras Date: Sat, 21 Dec 2024 21:08:35 -0500 Subject: [PATCH 4/5] change-default-types --- .../com/clickhouse/jdbc/ResultSetImpl.java | 30 +++++------- .../clickhouse/jdbc/internal/JdbcUtils.java | 49 +++++++++++++++++++ .../com/clickhouse/jdbc/DataTypeTests.java | 29 ++++++----- 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index a33dc1b59..9eb91fe7d 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -608,18 +608,7 @@ public Object getObject(int columnIndex) throws SQLException { @Override public Object getObject(String columnLabel) throws SQLException { - checkClosed(); - try { - if (reader.hasValue(columnLabel)) { - wasNull = false; - return reader.readValue(columnLabel); - } else { - wasNull = true; - return null; - } - } catch (Exception e) { - throw ExceptionUtils.toSqlState(String.format("SQL: [%s]; Method: getObject(%s)", parentStatement.getLastSql(), columnLabel), e); - } + return getObject(columnLabel, JdbcUtils.convertToJavaClass(getSchema().getColumnByName(columnLabel).getDataType())); } @Override @@ -1179,11 +1168,6 @@ public java.sql.Array getArray(int columnIndex) throws SQLException { @Override public Object getObject(String columnLabel, Map> map) throws SQLException { checkClosed(); - log.debug("getObject(columnLabel={}, map={})", columnLabel, map); - if (map == null) { - return getObject(columnLabel); - } - return getObject(columnLabel, map.get(JdbcUtils.convertToSqlType(getSchema().getColumnByName(columnLabel).getDataType()).getName())); } @@ -1647,7 +1631,17 @@ public T getObject(int columnIndex, Class type) throws SQLException { public T getObject(String columnLabel, Class type) throws SQLException { checkClosed(); try { - return (T) JdbcUtils.convert(getObject(columnLabel), type); + if (reader.hasValue(columnLabel)) { + wasNull = false; + if (type == null) {//As a fallback, try to get the value as is + return reader.readValue(columnLabel); + } + + return (T) JdbcUtils.convert(reader.readValue(columnLabel), type); + } else { + wasNull = true; + return null; + } } catch (Exception e) { throw ExceptionUtils.toSqlState(e); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index d4c0737dd..b5a40a6e6 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -13,6 +13,7 @@ import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -53,6 +54,46 @@ private static Map generateTypeMap() { return map; } + private static final Map> SQL_TYPE_TO_CLASS_MAP = generateClassMap(); + private static Map> generateClassMap() { + Map> map = new HashMap<>(); + map.put(JDBCType.CHAR, String.class); + map.put(JDBCType.VARCHAR, String.class); + map.put(JDBCType.LONGVARCHAR, String.class); + map.put(JDBCType.NUMERIC, java.math.BigDecimal.class); + map.put(JDBCType.DECIMAL, java.math.BigDecimal.class); + map.put(JDBCType.BIT, Boolean.class); + map.put(JDBCType.BOOLEAN, Boolean.class); + map.put(JDBCType.TINYINT, Integer.class); + map.put(JDBCType.SMALLINT, Integer.class); + map.put(JDBCType.INTEGER, Integer.class); + map.put(JDBCType.BIGINT, Long.class); + map.put(JDBCType.REAL, Float.class); + map.put(JDBCType.FLOAT, Double.class); + map.put(JDBCType.DOUBLE, Double.class); + map.put(JDBCType.BINARY, byte[].class); + map.put(JDBCType.VARBINARY, byte[].class); + map.put(JDBCType.LONGVARBINARY, byte[].class); + map.put(JDBCType.DATE, Date.class); + map.put(JDBCType.TIME, java.sql.Time.class); + map.put(JDBCType.TIMESTAMP, java.sql.Timestamp.class); + map.put(JDBCType.TIME_WITH_TIMEZONE, java.sql.Time.class); + map.put(JDBCType.TIMESTAMP_WITH_TIMEZONE, java.sql.Timestamp.class); + map.put(JDBCType.CLOB, java.sql.Clob.class); + map.put(JDBCType.BLOB, java.sql.Blob.class); + map.put(JDBCType.ARRAY, java.sql.Array.class); + map.put(JDBCType.STRUCT, java.sql.Struct.class); + map.put(JDBCType.REF, java.sql.Ref.class); + map.put(JDBCType.DATALINK, java.net.URL.class); + map.put(JDBCType.ROWID, java.sql.RowId.class); + map.put(JDBCType.NCHAR, String.class); + map.put(JDBCType.NVARCHAR, String.class); + map.put(JDBCType.LONGNVARCHAR, String.class); + map.put(JDBCType.NCLOB, java.sql.NClob.class); + map.put(JDBCType.SQLXML, java.sql.SQLXML.class); + return map; + } + public static SQLType convertToSqlType(ClickHouseDataType clickhouseType) { if (clickhouseType == null) { return JDBCType.NULL; @@ -61,6 +102,10 @@ public static SQLType convertToSqlType(ClickHouseDataType clickhouseType) { return CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(clickhouseType, JDBCType.OTHER); } + public static Class convertToJavaClass(ClickHouseDataType clickhouseType) { + return SQL_TYPE_TO_CLASS_MAP.get(convertToSqlType(clickhouseType)); + } + public static String generateSqlTypeEnum(String columnName) { StringBuilder sql = new StringBuilder("multiIf("); @@ -157,6 +202,10 @@ public static Object convert(Object value, Class type) throws SQLException { return Float.parseFloat(value.toString()); } else if (type == Double.class || type == double.class) { return Double.parseDouble(value.toString()); + } else if (type == java.math.BigDecimal.class) { + return new java.math.BigDecimal(value.toString()); + } else if (type == byte[].class) { + return value.toString().getBytes(); } else if (type == LocalDate.class && value instanceof TemporalAccessor) { return LocalDate.from((TemporalAccessor) value); } else if (type == LocalDateTime.class && value instanceof TemporalAccessor) { 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 2945480f3..93eb0c452 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -23,6 +23,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; @@ -882,35 +883,41 @@ public void testTypeConversions() throws Exception { assertEquals(String.valueOf(rs.getObject(3, new HashMap>(){{put(JDBCType.FLOAT.getName(), Float.class);}})), "1.0"); assertEquals(rs.getDate(4), Date.valueOf("2024-12-01")); - assertTrue(rs.getObject(4) instanceof ZonedDateTime);//ZonedDateTime - assertEquals(String.valueOf(rs.getObject(4)), "2024-12-01T00:00Z[UTC]");//ZonedDateTime + assertTrue(rs.getObject(4) instanceof Date); + assertEquals(rs.getObject(4), Date.valueOf("2024-12-01")); + assertEquals(rs.getString(4), "2024-12-01T00:00Z[UTC]");//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>(){{put(JDBCType.DATE.getName(), LocalDate.class);}})), "2024-12-01"); assertEquals(rs.getTimestamp(5), Timestamp.valueOf("2024-12-01 12:34:56")); - assertTrue(rs.getObject(5) instanceof ZonedDateTime);//ZonedDateTime - assertEquals(String.valueOf(rs.getObject(5)), "2024-12-01T12:34:56Z[UTC]");//ZonedDateTime + 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>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56"); assertEquals(rs.getTimestamp(6), Timestamp.valueOf("2024-12-01 12:34:56.789")); - assertTrue(rs.getObject(6) instanceof ZonedDateTime);//ZonedDateTime - assertEquals(String.valueOf(rs.getObject(6)), "2024-12-01T12:34:56.789Z[UTC]");//ZonedDateTime + 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>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), LocalDateTime.class);}})), "2024-12-01T12:34:56.789"); assertEquals(rs.getTimestamp(7), Timestamp.valueOf("2024-12-01 12:34:56.789789")); - assertTrue(rs.getObject(7) instanceof ZonedDateTime);//ZonedDateTime - assertEquals(String.valueOf(rs.getObject(7)), "2024-12-01T12:34:56.789789Z[UTC]");//ZonedDateTime + 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>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), OffsetDateTime.class);}})), "2024-12-01T12:34:56.789789Z"); assertEquals(rs.getTimestamp(8), Timestamp.valueOf("2024-12-01 12:34:56.789789789")); - assertTrue(rs.getObject(8) instanceof ZonedDateTime);//ZonedDateTime - assertEquals(String.valueOf(rs.getObject(8)), "2024-12-01T12:34:56.789789789Z[UTC]");//ZonedDateTime + 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]"); assertEquals(rs.getObject(8, LocalDateTime.class), LocalDateTime.of(2024, 12, 1, 12, 34, 56, 789789789)); assertEquals(String.valueOf(rs.getObject(8, new HashMap>(){{put(JDBCType.TIMESTAMP_WITH_TIMEZONE.getName(), ZonedDateTime.class);}})), "2024-12-01T12:34:56.789789789Z[UTC]"); - } } } From 874249b5525db3b52aaddacc6fb6054861974dba Mon Sep 17 00:00:00 2001 From: Paultagoras Date: Sat, 21 Dec 2024 21:20:50 -0500 Subject: [PATCH 5/5] Update DataTypeTests.java --- jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java | 3 --- 1 file changed, 3 deletions(-) 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 93eb0c452..fc3f867fb 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -8,7 +8,6 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.Blob; import java.sql.Connection; import java.sql.Date; import java.sql.DriverManager; @@ -18,10 +17,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; -import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime;