diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java new file mode 100644 index 000000000..e94283cf9 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java @@ -0,0 +1,251 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.google.common.collect.ImmutableMap; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +public final class ValueConverters { + + + // > + private final Map, Map, Function>> classConverters; + + // + private final ImmutableMap, Function> numberConverters; + + public ValueConverters() { + + + ImmutableMap.Builder, Function> numberConvertersBuilder = ImmutableMap.builder(); + numberConvertersBuilder.put(String.class, this::convertNumberToString); + numberConvertersBuilder.put(Boolean.class, this::convertNumberToBoolean); + numberConvertersBuilder.put(byte.class, this::convertNumberToByte); + numberConvertersBuilder.put(short.class, this::convertNumberToShort); + numberConvertersBuilder.put(int.class, this::convertNumberToInt); + numberConvertersBuilder.put(long.class, this::convertNumberToLong); + numberConvertersBuilder.put(float.class, this::convertNumberToFloat); + numberConvertersBuilder.put(double.class, this::convertNumberToDouble); + numberConvertersBuilder.put(Byte.class, this::convertNumberToByte); + numberConvertersBuilder.put(Short.class, this::convertNumberToShort); + numberConvertersBuilder.put(Integer.class, this::convertNumberToInt); + numberConvertersBuilder.put(Long.class, this::convertNumberToLong); + numberConvertersBuilder.put(Float.class, this::convertNumberToFloat); + numberConvertersBuilder.put(Double.class, this::convertNumberToDouble); + numberConvertersBuilder.put(BigInteger.class, this::convertNumberToBigInteger); + numberConvertersBuilder.put(BigDecimal.class, this::convertNumberToBigDecimal); + + numberConverters = numberConvertersBuilder.build(); + + + ImmutableMap.Builder, Map, Function>> mapBuilder = ImmutableMap.builder(); + + mapBuilder.put(byte.class, numberConverters); + mapBuilder.put(short.class, numberConverters); + mapBuilder.put(int.class, numberConverters); + mapBuilder.put(long.class, numberConverters); + mapBuilder.put(float.class, numberConverters); + mapBuilder.put(double.class, numberConverters); + mapBuilder.put(Byte.class, numberConverters); + mapBuilder.put(Short.class, numberConverters); + mapBuilder.put(Integer.class, numberConverters); + mapBuilder.put(Long.class, numberConverters); + mapBuilder.put(Float.class, numberConverters); + mapBuilder.put(Double.class, numberConverters); + mapBuilder.put(BigInteger.class, numberConverters); + mapBuilder.put(BigDecimal.class, numberConverters); + + ImmutableMap.Builder, Function> booleanMapBuilder = ImmutableMap.builder(); + booleanMapBuilder.put(byte.class, this::convertBooleanToNumber); + booleanMapBuilder.put(short.class, this::convertBooleanToNumber); + booleanMapBuilder.put(int.class, this::convertBooleanToNumber); + booleanMapBuilder.put(long.class, this::convertBooleanToNumber); + booleanMapBuilder.put(float.class, this::convertBooleanToNumber); + booleanMapBuilder.put(double.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Byte.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Short.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Integer.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Long.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Float.class, this::convertBooleanToNumber); + booleanMapBuilder.put(Double.class, this::convertBooleanToNumber); + booleanMapBuilder.put(BigInteger.class, this::convertBooleanToNumber); + booleanMapBuilder.put(BigDecimal.class, this::convertBooleanToNumber); + booleanMapBuilder.put(String.class, this::convertBooleanToString); + booleanMapBuilder.put(Boolean.class, this::convertBooleanToBoolean); + booleanMapBuilder.put(boolean.class, this::convertBooleanToBoolean); + + mapBuilder.put(Boolean.class, booleanMapBuilder.build()); + mapBuilder.put(boolean.class, booleanMapBuilder.build()); + + ImmutableMap.Builder, Function> stringMapBuilder = ImmutableMap.builder(); + stringMapBuilder.put(byte.class, this::convertStringToByte); + stringMapBuilder.put(short.class, this::convertStringToShort); + stringMapBuilder.put(int.class, this::convertStringToInt); + stringMapBuilder.put(long.class, this::convertStringToLong); + stringMapBuilder.put(float.class, this::convertStringToFloat); + stringMapBuilder.put(double.class, this::convertStringToDouble); + stringMapBuilder.put(Byte.class, this::convertStringToByte); + stringMapBuilder.put(Short.class, this::convertStringToShort); + stringMapBuilder.put(Integer.class, this::convertStringToInt); + stringMapBuilder.put(Long.class, this::convertStringToLong); + stringMapBuilder.put(Float.class, this::convertStringToFloat); + stringMapBuilder.put(Double.class, this::convertStringToDouble); + stringMapBuilder.put(Boolean.class, this::convertStringToBoolean); + stringMapBuilder.put(String.class, this::convertStringToString); + stringMapBuilder.put(byte[].class, this::convertStringToBytes); + stringMapBuilder.put(URL.class, this::convertStringToURL); + mapBuilder.put(String.class, stringMapBuilder.build()); + + mapBuilder.put(java.sql.Date.class, ImmutableMap.of(java.sql.Date.class, this::convertSqlDateToSqlDate, + String.class, this::convertDateToString)); + mapBuilder.put(Time.class, ImmutableMap.of(Time.class, this::convertSqlTimeToSqlTime, + String.class, this::convertTimeToString)); + mapBuilder.put(Timestamp.class, ImmutableMap.of(Timestamp.class, this::convertSqlTimestampToSqlTimestamp, + String.class, this::convertTimestampToString)); + + classConverters = mapBuilder.build(); + } + + // Boolean to any + public Boolean convertBooleanToBoolean(Object value) { + return (Boolean) value; + } + + public Number convertBooleanToNumber(Object value) { + return ((Boolean) value) ? 1L : 0L; + } + + public String convertBooleanToString(Object value) { + return String.valueOf(value); + } + + // String to any + public String convertStringToString(Object value) { + return (String) value; + } + + public byte[] convertStringToBytes(Object value) { + return ((String) value).getBytes(); + } + + public boolean convertStringToBoolean(Object value) { + return Boolean.parseBoolean((String) value); + } + + public byte convertStringToByte(Object value) { + return Byte.parseByte((String) value); + } + + public short convertStringToShort(Object value) { + return Short.parseShort((String) value); + } + + public int convertStringToInt(Object value) { + return Integer.parseInt((String) value); + } + + public long convertStringToLong(Object value) { + return Long.parseLong((String) value); + } + + public float convertStringToFloat(Object value) { + return Float.parseFloat((String) value); + } + + public double convertStringToDouble(Object value) { + return Double.parseDouble((String) value); + } + + public URL convertStringToURL(Object value) { + try { + return new URL((String) value); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + // Number to any + public String convertNumberToString(Object value) { + return String.valueOf(value); + } + + public boolean convertNumberToBoolean(Object value) { + return ((Number) value).floatValue() != 0.0f; + } + + public byte convertNumberToByte(Object value) { + return ((Number) value).byteValue(); + } + + public short convertNumberToShort(Object value) { + return ((Number) value).shortValue(); + } + + public int convertNumberToInt(Object value) { + return ((Number) value).intValue(); + } + + public long convertNumberToLong(Object value) { + return ((Number) value).longValue(); + } + + public float convertNumberToFloat(Object value) { + return ((Number) value).floatValue(); + } + + public double convertNumberToDouble(Object value) { + return ((Number) value).doubleValue(); + } + + public BigInteger convertNumberToBigInteger(Object value) { + return BigInteger.valueOf(((Number) value).longValue()); + } + + public BigDecimal convertNumberToBigDecimal(Object value) { + return BigDecimal.valueOf(((Number) value).doubleValue()); + } + + // Date & Time converters + public java.sql.Date convertSqlDateToSqlDate(Object value) { + return (java.sql.Date) value; + } + + public Time convertSqlTimeToSqlTime(Object value) { + return (Time) value; + } + + public Timestamp convertSqlTimestampToSqlTimestamp(Object value) { + return (Timestamp) value; + } + + public String convertDateToString(Object value) { + return value.toString(); + } + + public String convertTimeToString(Object value) { + return value.toString(); + } + + public String convertTimestampToString(Object value) { + return value.toString(); + } + + /** + * Returns the converter map for the given source type. + * Map contains target type and converter function. For example, if source type is boolean then map will contain all + * converters that support converting boolean to target type. + * @param type - source type + * @return - map of target type and converter function + */ + public Map, Function> getConvertersForType(Class type) { + return classConverters.getOrDefault(type, Collections.emptyMap()); + } +} 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 799417a69..cbc07d28c 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 @@ -77,6 +77,8 @@ private static Map generateTypeMap() { map.put(ClickHouseDataType.DateTime, JDBCType.TIMESTAMP); map.put(ClickHouseDataType.DateTime32, JDBCType.TIMESTAMP); map.put(ClickHouseDataType.DateTime64, JDBCType.TIMESTAMP); + map.put(ClickHouseDataType.Time, JDBCType.TIME); + map.put(ClickHouseDataType.Time64, JDBCType.TIME); map.put(ClickHouseDataType.Array, JDBCType.ARRAY); map.put(ClickHouseDataType.Nested, JDBCType.ARRAY); map.put(ClickHouseDataType.Map, JDBCType.JAVA_OBJECT); @@ -106,7 +108,7 @@ private static Map> generateClassMap() { 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.FLOAT, Float.class); map.put(JDBCType.DOUBLE, Double.class); map.put(JDBCType.BINARY, byte[].class); map.put(JDBCType.VARBINARY, byte[].class); @@ -457,7 +459,7 @@ public Object getValue(int i) { } } - private static Object[] arrayToObjectArray(Object array) { + public static Object[] arrayToObjectArray(Object array) { if (array == null) { return null; } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java index fc61feebd..ac82a3fd2 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java @@ -22,6 +22,7 @@ public class Array implements java.sql.Array { private final String elementTypeName; private boolean valid; private final ClickHouseDataType baseDataType; + private ArrayResultSet arrayResultSet; public Array(ClickHouseColumn column, Object[] elements) throws SQLException { this.column = column; @@ -88,8 +89,12 @@ public Object getArray(long index, int count, Map> map) throws } @Override - public ResultSet getResultSet() throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSet() is not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); + public synchronized ResultSet getResultSet() throws SQLException { + ensureValid(); + if (arrayResultSet == null) { + arrayResultSet = new ArrayResultSet(array, column); + } + return arrayResultSet; } @Override @@ -99,7 +104,8 @@ public ResultSet getResultSet(Map> map) throws SQLException { @Override public ResultSet getResultSet(long index, int count) throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSet(long, int) is not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED); + ensureValid(); + return new ArrayResultSet(getArray(index, count), column); } @Override diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java new file mode 100644 index 000000000..e36571215 --- /dev/null +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java @@ -0,0 +1,1250 @@ +package com.clickhouse.jdbc.types; + +import com.clickhouse.client.api.data_formats.internal.ValueConverters; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.ClickHouseDataType; +import com.clickhouse.jdbc.internal.ExceptionUtils; +import com.clickhouse.jdbc.internal.JdbcUtils; +import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLType; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class ArrayResultSet implements ResultSet { + + private final Object array; + private final int length; + private int pos; + private boolean closed; + private ResultSetMetaDataImpl metadata; + + private static final ClickHouseColumn INDEX_COLUMN = ClickHouseColumn.of("INDEX", ClickHouseDataType.UInt32, false, 0, 0); + private static final String VALUE_COLUMN = "VALUE"; + private static final ValueConverters defaultValueConverters = new ValueConverters(); + + private int fetchDirection = ResultSet.FETCH_FORWARD; + private int fetchSize = 0; + private boolean wasNull = false; + private Map, Function> converterMap; + private final Map, Function> indexConverterMap; + private final ClickHouseDataType componentDataType; + private final Class defaultClass; + private final ClickHouseColumn column; + private final int columnCount; + + public ArrayResultSet(Object array, ClickHouseColumn column) { + this.array = array; + this.length = java.lang.reflect.Array.getLength(array); + this.pos = -1; + this.column = column; + this.columnCount = 2; // INDEX, VALUE + + List nestedColumns = column.getNestedColumns(); + ClickHouseColumn valueColumn = column.getArrayNestedLevel() == 1 ? column.getArrayBaseColumn() : nestedColumns.get(0); + this.metadata = new ResultSetMetaDataImpl(Arrays.asList(INDEX_COLUMN, ClickHouseColumn.parse(VALUE_COLUMN + " " + + valueColumn.getOriginalTypeName()).get(0)) + , "", "", "", JdbcUtils.DATA_TYPE_CLASS_MAP); + this.componentDataType = valueColumn.getDataType(); + this.defaultClass = JdbcUtils.DATA_TYPE_CLASS_MAP.get(componentDataType); + indexConverterMap = defaultValueConverters.getConvertersForType(Integer.class); + } + + @Override + public boolean next() throws SQLException { + if (pos == length || length == 0) { + return false; + } + pos++; + return true; + } + + private void checkColumnIndex(int columnIndex) throws SQLException { + if (columnIndex < 1 || columnIndex > columnCount) { + throw new SQLException("Invalid column index: " + columnIndex); + } + } + + private void checkRowPosition() throws SQLException { + if (pos < 0 || pos >= length) { + throw new SQLException("No current row"); + } + } + + private Map, Function> initValueConverterMapIfNeeded(Object nonNullValue) { + if (converterMap == null) { + if (array.getClass().getComponentType() == Object.class) { + converterMap = defaultValueConverters.getConvertersForType(nonNullValue.getClass()); + } else { + converterMap = defaultValueConverters.getConvertersForType(array.getClass().getComponentType()); + } + } + return converterMap; + } + + private Object convertValue(Object value, Class targetType, Map, Function> valueConverterMap) throws SQLException { + if (value == null || targetType == value.getClass() || targetType == Object.class) { + return value; + } + + Function converter = valueConverterMap.get(targetType); + if (converter != null) { + try { + return converter.apply(value); + } catch (Exception e) { + throw new SQLException("Failed to convert value of " + value.getClass() + " to " + targetType, + ExceptionUtils.SQL_STATE_DATA_EXCEPTION, e); + } + } else { + throw new SQLException("Value of " + value.getClass() + " cannot be converted to " + targetType); + } + } + + private Object getValueAsObject(int columnIndex, Class type, Object defaultValue) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + + if (columnIndex == 1) { + Integer value = pos + 1; + return convertValue(value, type, indexConverterMap); + } else { + Object value = java.lang.reflect.Array.get(array, pos); + wasNull = value == null; + if (value == null) { + return defaultValue; + } + + if (type == Array.class) { + ClickHouseColumn nestedColumn = + column.getArrayNestedLevel() == 1 ? column.getArrayBaseColumn() : column.getNestedColumns().get(0); + return new com.clickhouse.jdbc.types.Array(nestedColumn, JdbcUtils.arrayToObjectArray(value)); + } else { + Map, Function> valueConverterMap = initValueConverterMapIfNeeded(value); + return convertValue(value, type, valueConverterMap); + } + } + } + + private void throwReadOnlyException() throws SQLException { + throw new SQLException("ResultSet is read-only"); + } + + private void throwUnsupportedIndexOperation(int columnIndex, String operation) throws SQLException { + if (columnIndex == 1) { + throw new SQLFeatureNotSupportedException("operation " + operation + " is not supported on INDEX column"); + } + } + + @Override + public void close() throws SQLException { + this.closed = true; + } + + @Override + public boolean wasNull() throws SQLException { + return wasNull; + } + + @Override + public String getString(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + return (String) getValueAsObject(columnIndex, String.class, null); + } + + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getBoolean"); + return (Boolean) getValueAsObject(columnIndex, Boolean.class, false); + } + + @Override + public byte getByte(int columnIndex) throws SQLException { + if (columnIndex == 1) { + if (pos + 1 < Byte.MAX_VALUE) { + return (byte) getRow(); + } else { + throw new SQLException("INDEX column value too big and cannot be retrieved as byte"); + } + } + return ((Number) getValueAsObject(columnIndex, Byte.class, 0)).byteValue(); + } + + @Override + public short getShort(int columnIndex) throws SQLException { + if (columnIndex == 1) { + if (pos + 1 < Short.MAX_VALUE) { + return (short) getRow(); + } else { + throw new SQLException("INDEX column value too big and cannot be retrieved as short"); + } + } + return ((Number) getValueAsObject(columnIndex, Short.class, 0)).shortValue(); + } + + @Override + public int getInt(int columnIndex) throws SQLException { + if (columnIndex == 1) { + return getRow(); + } + return ((Number) getValueAsObject(columnIndex, Integer.class, 0)).intValue(); + } + + @Override + public long getLong(int columnIndex) throws SQLException { + if (columnIndex == 1) { + return getRow(); + } + return ((Number) getValueAsObject(columnIndex, Long.class, 0L)).longValue(); + } + + @Override + public float getFloat(int columnIndex) throws SQLException { + if (columnIndex == 1) { + return (float) getRow(); + } + return ((Number) getValueAsObject(columnIndex, Float.class, 0.0f)).floatValue(); + } + + @Override + public double getDouble(int columnIndex) throws SQLException { + if (columnIndex == 1) { + return getRow(); + } + return ((Number) getValueAsObject(columnIndex, Double.class, 0.0d)).doubleValue(); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { + if (columnIndex == 1) { + return BigDecimal.valueOf(getRow()); + } + return (BigDecimal) getValueAsObject(columnIndex, BigDecimal.class, null); + } + + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getBytes"); + return (byte[]) getValueAsObject(columnIndex, byte[].class, null); + } + + @Override + public Date getDate(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getDate"); + return (Date) getValueAsObject(columnIndex, Date.class, null); + } + + @Override + public Time getTime(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getTime"); + return (Time) getValueAsObject(columnIndex, Time.class, null); + } + + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getTimestamp"); + return (Timestamp) getValueAsObject(columnIndex, Timestamp.class, null); + } + + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getAsciiStream"); + throw new SQLFeatureNotSupportedException("getAsciiStream is not implemented"); + } + + @Override + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getUnicodeStream"); + throw new SQLFeatureNotSupportedException("getUnicodeStream is not implemented"); + } + + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getBinaryStream"); + throw new SQLFeatureNotSupportedException("getBinaryStream is not implemented"); + } + + private int getColumnIndex(String columnLabel) throws SQLException { + if (columnLabel.equalsIgnoreCase(INDEX_COLUMN.getColumnName())) { + return 1; + } + if (columnLabel.equalsIgnoreCase(VALUE_COLUMN)) { + return 2; + } + + throw new SQLException("Unknown column label `" + columnLabel + "`"); + } + + @Override + public String getString(String columnLabel) throws SQLException { + return getString(getColumnIndex(columnLabel)); + } + + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + return getBoolean(getColumnIndex(columnLabel)); + } + + @Override + public byte getByte(String columnLabel) throws SQLException { + return getByte(getColumnIndex(columnLabel)); + } + + @Override + public short getShort(String columnLabel) throws SQLException { + return getShort(getColumnIndex(columnLabel)); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + return getInt(getColumnIndex(columnLabel)); + } + + @Override + public long getLong(String columnLabel) throws SQLException { + return getLong(getColumnIndex(columnLabel)); + } + + @Override + public float getFloat(String columnLabel) throws SQLException { + return getFloat(getColumnIndex(columnLabel)); + } + + @Override + public double getDouble(String columnLabel) throws SQLException { + return getDouble(getColumnIndex(columnLabel)); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { + return getBigDecimal(getColumnIndex(columnLabel), scale); + } + + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + return getBytes(getColumnIndex(columnLabel)); + } + + @Override + public Date getDate(String columnLabel) throws SQLException { + return getDate(getColumnIndex(columnLabel)); + } + + @Override + public Time getTime(String columnLabel) throws SQLException { + return getTime(getColumnIndex(columnLabel)); + } + + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + return getTimestamp(getColumnIndex(columnLabel)); + } + + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + return getAsciiStream(getColumnIndex(columnLabel)); + } + + @Override + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + return getUnicodeStream(getColumnIndex(columnLabel)); + } + + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + return getBinaryStream(getColumnIndex(columnLabel)); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return null; + } + + @Override + public void clearWarnings() throws SQLException { + + } + + @Override + public String getCursorName() throws SQLException { + return ""; + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return metadata; + } + + @Override + public Object getObject(int columnIndex) throws SQLException { + Class targetType = columnIndex == 1 ? Integer.class : defaultClass; + return getObject(columnIndex, targetType); + } + + @Override + public Object getObject(String columnLabel) throws SQLException { + return getObject(getColumnIndex(columnLabel)); + } + + @Override + public int findColumn(String columnLabel) throws SQLException { + return getColumnIndex(columnLabel); + } + + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getCharacterStream"); + throw new SQLFeatureNotSupportedException("getCharacterStream is not implemented"); + } + + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + return getCharacterStream(getColumnIndex(columnLabel)); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getBigDecimal"); + return (BigDecimal) getValueAsObject(columnIndex, BigDecimal.class, null); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + return getBigDecimal(getColumnIndex(columnLabel)); + } + + @Override + public boolean isBeforeFirst() throws SQLException { + return pos == -1; + } + + @Override + public boolean isAfterLast() throws SQLException { + return pos >= length; + } + + @Override + public boolean isFirst() throws SQLException { + return pos == 0; + } + + @Override + public boolean isLast() throws SQLException { + return length > 0 && pos == length - 1; + } + + @Override + public void beforeFirst() throws SQLException { + pos = -1; + } + + @Override + public void afterLast() throws SQLException { + pos = length; + } + + @Override + public boolean first() throws SQLException { + if (length > 0) { + pos = 0; + return true; + } + return false; + } + + @Override + public boolean last() throws SQLException { + if (length > 0) { + pos = length - 1; + return true; + } + return false; + } + + @Override + public int getRow() throws SQLException { + if (isBeforeFirst() || isAfterLast()) { + return 0; + } + return pos + 1; + } + + @Override + public boolean absolute(int row) throws SQLException { + pos = row - 1; + return !(isAfterLast() || isBeforeFirst()); + } + + @Override + public boolean relative(int rows) throws SQLException { + return absolute((pos + 1) + rows); + } + + @Override + public boolean previous() throws SQLException { + return absolute(pos); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + if (!(direction == FETCH_FORWARD || direction == FETCH_REVERSE || direction == FETCH_UNKNOWN)) { + throw new SQLException("Invalid fetch direction: " + direction + ". Should be one of [ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, ResultSet.FETCH_UNKNOWN]"); + } + this.fetchDirection = direction; + } + + @Override + public int getFetchDirection() throws SQLException { + return fetchDirection; + } + + @Override + public void setFetchSize(int rows) throws SQLException { + // ignored as we fetched array already + this.fetchSize = rows; + } + + @Override + public int getFetchSize() throws SQLException { + return fetchSize; + } + + @Override + public int getType() throws SQLException { + return ResultSet.TYPE_SCROLL_INSENSITIVE; + } + + @Override + public int getConcurrency() throws SQLException { + return ResultSet.CONCUR_READ_ONLY; + } + + @Override + public boolean rowUpdated() throws SQLException { + return false; + } + + @Override + public boolean rowInserted() throws SQLException { + return false; + } + + @Override + public boolean rowDeleted() throws SQLException { + return false; + } + + @Override + public void updateNull(int columnIndex) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateString(int columnIndex, String x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNull(String columnLabel) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateString(String columnLabel, String x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void insertRow() throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateRow() throws SQLException { + throwReadOnlyException(); + } + + @Override + public void deleteRow() throws SQLException { + throwReadOnlyException(); + } + + @Override + public void refreshRow() throws SQLException { + throw new SQLFeatureNotSupportedException("refreshRow is not supported on ResultSet produced from Array object"); + } + + @Override + public void cancelRowUpdates() throws SQLException { + throw new SQLFeatureNotSupportedException("cancelRowUpdates is not supported"); + } + + @Override + public void moveToInsertRow() throws SQLException { + throwReadOnlyException(); + } + + @Override + public void moveToCurrentRow() throws SQLException { + throwReadOnlyException(); + } + + @Override + public Statement getStatement() throws SQLException { + return null; // null as it is produced from an Array object + } + + @Override + public Object getObject(int columnIndex, Map> map) throws SQLException { + Class type = map.get(componentDataType.getName()); + if (type == null) { + SQLType sqlType = JdbcUtils.CLICKHOUSE_TO_SQL_TYPE_MAP.get(componentDataType); + if (sqlType != null) { + type = map.get(sqlType.getName()); + } + + if (type == null) { + // try to find by alias + for (String alias : componentDataType.getAliases()) { + type = map.get(alias); + if (type != null) { + break; + } + } + } + + if (type == null) { + type = defaultClass; + } + } + + return getObject(columnIndex, type); + } + + @Override + public Ref getRef(int columnIndex) throws SQLException { + checkRowPosition(); + checkColumnIndex(columnIndex); + throwUnsupportedIndexOperation(columnIndex, "getRef"); + throw new SQLFeatureNotSupportedException("getRef is not implemented"); + } + + @Override + public Blob getBlob(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getBlob"); + throw new SQLFeatureNotSupportedException("getBlob is not implemented"); + } + + @Override + public Clob getClob(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getClob"); + throw new SQLFeatureNotSupportedException("getClob is not implemented"); + } + + @Override + public Array getArray(int columnIndex) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getArray"); + return (Array) getValueAsObject(columnIndex, Array.class, null); + } + + @Override + public Object getObject(String columnLabel, Map> map) throws SQLException { + return getObject(getColumnIndex(columnLabel), map); + } + + @Override + public Ref getRef(String columnLabel) throws SQLException { + return getRef(getColumnIndex(columnLabel)); + } + + @Override + public Blob getBlob(String columnLabel) throws SQLException { + return getBlob(getColumnIndex(columnLabel)); + } + + @Override + public Clob getClob(String columnLabel) throws SQLException { + return getClob(getColumnIndex(columnLabel)); + } + + @Override + public Array getArray(String columnLabel) throws SQLException { + return getArray(getColumnIndex(columnLabel)); + } + + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getDate"); + return getDate(columnIndex); + } + + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException { + return getDate(getColumnIndex(columnLabel), cal); + } + + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getTime"); + return getTime(columnIndex); + } + + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException { + return getTime(getColumnIndex(columnLabel), cal); + } + + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { + throwUnsupportedIndexOperation(columnIndex, "getTimestamp"); + return (Timestamp) getValueAsObject(columnIndex, Timestamp.class, null); + } + + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { + return getTimestamp(getColumnIndex(columnLabel), cal); + } + + @Override + public URL getURL(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getURL"); + String value = getString(columnIndex); + try { + return new URL(value); + } catch (MalformedURLException e) { + throw new SQLException("Invalid URL value", ExceptionUtils.SQL_STATE_DATA_EXCEPTION, e); + } + } + + @Override + public URL getURL(String columnLabel) throws SQLException { + return getURL(getColumnIndex(columnLabel)); + } + + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public RowId getRowId(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getRowId"); + throw new SQLFeatureNotSupportedException("getRowId is not implemented"); + } + + @Override + public RowId getRowId(String columnLabel) throws SQLException { + return getRowId(getColumnIndex(columnLabel)); + } + + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public int getHoldability() throws SQLException { + return ResultSet.HOLD_CURSORS_OVER_COMMIT; + } + + @Override + public boolean isClosed() throws SQLException { + return closed; + } + + @Override + public void updateNString(int columnIndex, String nString) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNString(String columnLabel, String nString) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(int columnIndex, NClob nClob) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(String columnLabel, NClob nClob) throws SQLException { + throwReadOnlyException(); + } + + @Override + public NClob getNClob(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getNClob"); + throw new SQLFeatureNotSupportedException("getNClob is not implemented"); + } + + @Override + public NClob getNClob(String columnLabel) throws SQLException { + return getNClob(getColumnIndex(columnLabel)); + } + + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getSQLXML"); + throw new SQLFeatureNotSupportedException("getSQLXML is not implemented"); + } + + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + return getSQLXML(getColumnIndex(columnLabel)); + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { + throwReadOnlyException(); + } + + @Override + public String getNString(int columnIndex) throws SQLException { + return getString(columnIndex); + } + + @Override + public String getNString(String columnLabel) throws SQLException { + return getString(getColumnIndex(columnLabel)); + } + + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + throwUnsupportedIndexOperation(columnIndex, "getNCharacterStream"); + throw new SQLFeatureNotSupportedException("getNCharacterStream is not implemented"); + } + + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + return getNCharacterStream(getColumnIndex(columnLabel)); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(int columnIndex, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateClob(String columnLabel, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(int columnIndex, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public void updateNClob(String columnLabel, Reader reader) throws SQLException { + throwReadOnlyException(); + } + + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + checkColumnIndex(columnIndex); + checkRowPosition(); + return (T) getValueAsObject(columnIndex, type, null); + } + + @Override + public T getObject(String columnLabel, Class type) throws SQLException { + return getObject(getColumnIndex(columnLabel), type); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } +} diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java index 37b5dd1b6..9da719d0e 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java @@ -397,10 +397,6 @@ public void testCreateArray() throws SQLException { assertEquals(arrayValue.getBaseType(), JDBCType.OTHER.getVendorTypeNumber()); assertThrows(SQLFeatureNotSupportedException.class, () -> arrayValue.getArray(null)); assertThrows(SQLFeatureNotSupportedException.class, () -> arrayValue.getArray(0, 1, null)); - assertThrows(SQLFeatureNotSupportedException.class, arrayValue::getResultSet); - assertThrows(SQLFeatureNotSupportedException.class, () -> arrayValue.getResultSet(0, 1)); - assertThrows(SQLFeatureNotSupportedException.class, () -> arrayValue.getResultSet(null)); - assertThrows(SQLFeatureNotSupportedException.class, () -> arrayValue.getResultSet(0, 1, null)); Assert.expectThrows(SQLException.class, () -> arrayValue.getArray(-1, 1)); Assert.expectThrows(SQLException.class, () -> arrayValue.getArray(0, -1)); 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 8c8ac2c6b..4216eb061 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -835,11 +835,11 @@ public void testFloatTypes() throws SQLException { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_floats ORDER BY order")) { assertTrue(rs.next()); - assertEquals(rs.getObject("float32"), -3.402823E38d); + assertEquals(rs.getObject("float32"), -3.402823E38f); assertEquals(rs.getObject("float64"), Double.valueOf(-1.7976931348623157E308)); assertTrue(rs.next()); - assertEquals(rs.getObject("float32"), 3.402823E38d); + assertEquals(rs.getObject("float32"), 3.402823E38f); assertEquals(rs.getObject("float64"), Double.valueOf(1.7976931348623157E308)); assertTrue(rs.next()); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java index a97e4d492..4c3262dd6 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java @@ -1,5 +1,7 @@ package com.clickhouse.jdbc; +import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.data.ClickHouseVersion; import org.testng.Assert; import org.testng.annotations.Test; @@ -28,6 +30,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +@Test(groups = "integration") public class ResultSetImplTest extends JdbcIntegrationTest { @Test(groups = "integration") @@ -360,4 +363,122 @@ public void testGetMetadata() throws SQLException { } } } + + @Test(groups = {"integration"}) + public void testGetResultSetFromArray() throws Exception { + try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("select [1, 2, null, 4]::Array(Nullable(UInt16)) as v")) { + assertTrue(rs.next()); + + Array array = rs.getArray("v"); + Assert.assertNotNull(array); + Assert.assertEquals(array.getBaseType(), Types.INTEGER); + Assert.assertEquals(array.getBaseTypeName(), "Nullable(UInt16)"); + + Integer[] array2 = (Integer[]) array.getArray(); + + ResultSet rs2 = array.getResultSet(); + Assert.assertTrue(rs2.isBeforeFirst()); + Assert.assertFalse(rs2.isAfterLast()); + String valueColumn = rs2.getMetaData().getColumnName(2); + for (int i = 0; i < array2.length; i++) { + rs2.next(); + if (i == 2 ) { + rs2.getInt(valueColumn); + Assert.assertTrue(rs2.wasNull()); + } else { + Assert.assertEquals(rs2.getInt(valueColumn), array2[i]); + Assert.assertFalse(rs2.wasNull()); + } + } + } + } + } + + @Test(groups = {"integration"}) + public void testGetResultSetFromArrayDate() throws Exception { + try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { + // date array + try (ResultSet rs = stmt.executeQuery("select [toDate('2020-01-01'), toDate('2020-01-02')] as v")) { + + assertTrue(rs.next()); + Array array = rs.getArray("v"); + Assert.assertNotNull(array); + Assert.assertEquals(array.getBaseType(), Types.DATE); + Assert.assertEquals(array.getBaseTypeName(), "Date"); + + Object[] resultArray = (Object[]) array.getArray(); + Assert.assertEquals(resultArray.length, 2); + Assert.assertEquals(resultArray[0], Date.valueOf("2020-01-01")); + Assert.assertEquals(resultArray[1], Date.valueOf("2020-01-02")); + + ResultSet rs2 = array.getResultSet(); + final String valueColumn = rs2.getMetaData().getColumnName(2); + for (int i = 0; i < resultArray.length; i++) { + rs2.next(); + Assert.assertEquals(rs2.getDate(valueColumn), resultArray[i]); + } + } + } + } + + @Test(groups = {"integration"}, enabled = false) + public void testGetResultSetFromArrayTime() throws Exception { + if (ClickHouseVersion.of(getServerVersion()).check("(,25.5]")) { + return; // Time64 introduced in 25.6 + } + + Properties properties = new Properties(); + properties.put(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1"); + try (Connection conn = getJdbcConnection(properties); Statement stmt = conn.createStatement()) { + // time array + try (ResultSet rs = stmt.executeQuery("select ['14:30:25'::Time, '17:30:25'::Time] as v")) { + + assertTrue(rs.next()); + Array array = rs.getArray("v"); + Assert.assertNotNull(array); + Assert.assertEquals(array.getBaseType(), Types.TIME); + Assert.assertEquals(array.getBaseTypeName(), "Time"); + + Object[] resultArray = (Object[]) array.getArray(); + Assert.assertEquals(resultArray.length, 2); + Assert.assertEquals(resultArray[0], Time.valueOf("14:30:25")); + Assert.assertEquals(resultArray[1], Time.valueOf("17:30:25")); + + ResultSet rs2 = array.getResultSet(); + final String valueColumn = rs2.getMetaData().getColumnName(2); + for (int i = 0; i < resultArray.length; i++) { + rs2.next(); + Assert.assertEquals(rs2.getTime(valueColumn), resultArray[i]); + } + } + } + } + + @Test(groups = {"integration"}) + public void testGetResultSetFromArrayTimestamp() throws Exception { + try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { + // timestamp array + try (ResultSet rs = stmt.executeQuery("select [toDateTime('2020-01-01 00:00:00'), toDateTime('2020-01-01 00:00:01')] as v")) { + + assertTrue(rs.next()); + Array array = rs.getArray("v"); + Assert.assertNotNull(array); + Assert.assertEquals(array.getBaseType(), Types.TIMESTAMP); + Assert.assertEquals(array.getBaseTypeName(), "DateTime"); + + Object[] resultArray = (Object[]) array.getArray(); + Assert.assertEquals(resultArray.length, 2); + Assert.assertEquals(resultArray[0], Timestamp.valueOf("2020-01-01 00:00:00")); + Assert.assertEquals(resultArray[1], Timestamp.valueOf("2020-01-01 00:00:01")); + + ResultSet rs2 = array.getResultSet(); + final String valueColumn = rs2.getMetaData().getColumnName(2); + for (int i = 0; i < resultArray.length; i++) { + rs2.next(); + Assert.assertEquals(rs2.getTimestamp(valueColumn), resultArray[i]); + } + } + } + } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java new file mode 100644 index 000000000..077b1f78d --- /dev/null +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java @@ -0,0 +1,561 @@ +package com.clickhouse.jdbc.types; + +import com.clickhouse.data.ClickHouseColumn; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.GregorianCalendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.expectThrows; + +@Test(groups = {"unit"}) +public class ArrayResultSetTest { + + @Test + void testCursorNavigation() throws SQLException { + Integer[] array = {1, 2, 3, 4, 5}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Int32)").get(0)); + + assertEquals(rs.getFetchDirection(), ResultSet.FETCH_FORWARD); + rs.setFetchDirection(ResultSet.FETCH_REVERSE); + assertEquals(rs.getFetchDirection(), ResultSet.FETCH_REVERSE); + assertThrows(SQLException.class, () -> rs.setFetchDirection(123)); + assertEquals(rs.getType(), ResultSet.TYPE_SCROLL_INSENSITIVE); + assertEquals(rs.getConcurrency(), ResultSet.CONCUR_READ_ONLY); + rs.setFetchSize(10000); + assertEquals(rs.getFetchSize(), 10000); + assertEquals(rs.getHoldability(), ResultSet.HOLD_CURSORS_OVER_COMMIT); + assertFalse(rs.isClosed()); + + assertTrue(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); + assertThrows(SQLException.class, () -> rs.getShort(2)); + + rs.next(); + + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertTrue(rs.isFirst()); + assertFalse(rs.isLast()); + + assertEquals(rs.getShort(1), 1); // INDEX + assertEquals(rs.getShort(2), array[0].shortValue()); // VALUE + assertEquals(rs.getInt(1), rs.getRow()); + + assertTrue(rs.relative(2)); + assertEquals(rs.getRow(), 3); // INDEX + assertEquals(rs.getShort(2), array[2].shortValue()); // VALUE + + assertTrue(rs.previous()); + assertEquals(rs.getRow(), 2); // INDEX + assertEquals(rs.getShort(2), array[1].shortValue()); // VALUE + + + assertTrue(rs.absolute(array.length)); // INDEX - last element + assertEquals(rs.getRow(), array.length); // INDEX + assertEquals(rs.getShort(2), array[array.length - 1].shortValue()); // VALUE + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertTrue(rs.isLast()); + + + assertFalse(rs.relative(2)); + assertEquals(rs.getRow(), 0); // INDEX + assertFalse(rs.isBeforeFirst()); + assertTrue(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); + + rs.first(); + assertEquals(rs.getRow(), 1); // INDEX + assertEquals(rs.getShort(2), array[0].shortValue()); // VALUE + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertTrue(rs.isFirst()); + assertFalse(rs.isLast()); + + rs.last(); + assertEquals(rs.getRow(), array.length); // INDEX + assertEquals(rs.getShort(2), array[array.length - 1].shortValue()); // VALUE + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertTrue(rs.isLast()); + + rs.beforeFirst(); + assertEquals(rs.getRow(), 0); // INDEX + assertTrue(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); + + rs.afterLast(); + assertEquals(rs.getRow(), 0); // INDEX + assertTrue(rs.isAfterLast()); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); + + assertFalse(rs.next()); + assertThrows(SQLException.class, () -> rs.getShort(2)); + + assertFalse(rs.rowDeleted()); + assertFalse(rs.rowInserted()); + assertFalse(rs.rowUpdated()); + + rs.close(); + assertTrue(rs.isClosed()); + } + + @Test + void testNullValues() throws SQLException { + Integer[] array = {null, 1, null, 3, 4, 5}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Int32)").get(0)); + + rs.next(); + assertEquals(rs.getInt(2), 0); + assertTrue(rs.wasNull()); + assertTrue(rs.wasNull()); + + rs.next(); + assertTrue(rs.wasNull()); + assertEquals(rs.getInt(2), array[1]); + assertFalse(rs.wasNull()); + assertFalse(rs.wasNull()); + + rs.next(); + assertFalse(rs.wasNull()); + assertEquals(rs.getInt(2), 0); + assertTrue(rs.wasNull()); + assertTrue(rs.wasNull()); + } + + @Test(dataProvider = "testPrimitiveValues") + void testPrimitiveValues(Object array, ClickHouseColumn column) throws SQLException { + ArrayResultSet rs = new ArrayResultSet(array, column); + + expectThrows(SQLException.class, () -> rs.findColumn("something")); + String valueColumn = rs.getMetaData().getColumnLabel(2); + int len = java.lang.reflect.Array.getLength(array); + Class itemClass = array.getClass().getComponentType(); + for (int i = 0; i < len; i++) { + rs.next(); + Object value = Array.get(array, i); + Object actualValue = rs.getObject(valueColumn); + assertEquals(actualValue, value, "Actual value is " + actualValue.getClass() + " but expected " + value.getClass()); + if (itemClass.isPrimitive() && (itemClass != Boolean.class && itemClass != boolean.class)) { + Number number = ((Number) value); + assertEquals(rs.getByte(valueColumn), number.byteValue()); + assertEquals(rs.getShort(valueColumn), number.shortValue()); + assertEquals(rs.getInt(valueColumn), number.intValue()); + assertEquals(rs.getLong(valueColumn), number.longValue()); + assertEquals(rs.getFloat(valueColumn), number.floatValue()); + assertEquals(rs.getDouble(valueColumn), number.doubleValue()); + assertEquals(rs.getString(valueColumn), String.valueOf(value)); + } else if (Number.class.isAssignableFrom(itemClass)) { + Number number = ((Number) value); + assertEquals(rs.getByte(valueColumn), number.byteValue()); + assertEquals(rs.getShort(valueColumn), number.shortValue()); + assertEquals(rs.getInt(valueColumn), number.intValue()); + assertEquals(rs.getLong(valueColumn), number.longValue()); + assertEquals(rs.getFloat(valueColumn), number.floatValue()); + assertEquals(rs.getDouble(valueColumn), number.doubleValue()); + assertEquals(rs.getBigDecimal(valueColumn), BigDecimal.valueOf(number.doubleValue())); + } else if (itemClass == Boolean.class || itemClass == boolean.class) { + Number number = ((Boolean) value) ? 1 : 0; + assertEquals(rs.getBoolean(valueColumn), ((Boolean) value)); + assertEquals(rs.getByte(valueColumn), number.byteValue()); + assertEquals(rs.getShort(valueColumn), number.shortValue()); + assertEquals(rs.getInt(valueColumn), number.intValue()); + assertEquals(rs.getLong(valueColumn), number.longValue()); + assertEquals(rs.getFloat(valueColumn), number.floatValue()); + assertEquals(rs.getDouble(valueColumn), number.doubleValue()); + } + + String indexColumn = rs.getMetaData().getColumnName(1); + assertEquals(rs.getByte(indexColumn), i + 1); + assertEquals(rs.getShort(indexColumn), i + 1); + assertEquals(rs.getInt(indexColumn), i + 1); + assertEquals(rs.getLong(indexColumn), i + 1); + assertEquals(rs.getFloat(indexColumn), i + 1); + assertEquals(rs.getDouble(indexColumn), i + 1); + } + } + + @DataProvider + static Object[][] testPrimitiveValues() { + return new Object[][]{ + {(Object) new boolean[]{true, false, true}, ClickHouseColumn.parse("v Array(Bool)").get(0)}, + {(Object) new byte[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int8)").get(0)}, + {(Object) new short[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int16)").get(0)}, + {(Object) new int[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int32)").get(0)}, + {(Object) new long[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int64)").get(0)}, + {(Object) new double[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Float64)").get(0)}, + {(Object) new float[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Float32)").get(0)}, + {(Object) new String[]{"a", "b", "c"}, ClickHouseColumn.parse("v Array(String)").get(0)}, + {(Object) new Boolean[]{true, false, true}, ClickHouseColumn.parse("v Array(Bool)").get(0)}, + {(Object) new Byte[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int8)").get(0)}, + {(Object) new Short[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int16)").get(0)}, + {(Object) new Integer[]{1, 2, 3}, ClickHouseColumn.parse("v Array(Int32)").get(0)}, + {(Object) new Long[]{1L, 2L, 3L}, ClickHouseColumn.parse("v Array(Int64)").get(0)}, + {(Object) new Float[]{1.0F, 2.0F, 3.0F}, ClickHouseColumn.parse("v Array(Float32)").get(0)}, + {(Object) new Double[]{1.0D, 2.0D, 3.0D}, ClickHouseColumn.parse("v Array(Float64)").get(0)}, + {(Object) new String[]{"a", "b", "c"}, ClickHouseColumn.parse("v Array(String)").get(0)}, + }; + } + + @Test(dataProvider = "testPrimitiveMultiDimensionalValues") + void testPrimitiveMultiDimensionalValues(Object array, ClickHouseColumn column) throws SQLException { + ArrayResultSet rs = new ArrayResultSet(array, column); + + int len = java.lang.reflect.Array.getLength(array); + final String valueColumn = rs.getMetaData().getColumnName(2); + for (int i = 0; i < len; i++) { + rs.next(); + Object value = Array.get(array, i); + java.sql.Array sqlArray = (java.sql.Array) rs.getObject(valueColumn); + assertEquals(sqlArray.getArray(), value); + java.sql.Array sqlArray2 = rs.getArray(valueColumn); + assertEquals(sqlArray2.getArray(), value); + + ArrayResultSet nestedRs = (ArrayResultSet) sqlArray.getResultSet(); + for (int j = 0; j < len; j++) { + nestedRs.next(); + Object nestedValue = Array.get(value, j); + assertEquals(nestedRs.getObject(2), nestedValue); + } + } + } + + @DataProvider + static Object[][] testPrimitiveMultiDimensionalValues() { + return new Object[][]{ + {(Object) new boolean[][]{new boolean[]{true, false, true}, new boolean[]{true, false, true}}, ClickHouseColumn.parse("v Array(Array(Bool))").get(0)}, + {(Object) new byte[][]{new byte[]{1, 2, 3}, new byte[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Int8))").get(0)}, + {(Object) new short[][]{new short[]{1, 2, 3}, new short[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Int16))").get(0)}, + + {(Object) new int[][]{new int[]{1, 2, 3}, new int[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Int32))").get(0)}, + {(Object) new long[][]{new long[]{1, 2, 3}, new long[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Int64))").get(0)}, + {(Object) new float[][]{new float[]{1, 2, 3}, new float[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Float32))").get(0)}, + {(Object) new double[][]{new double[]{1, 2, 3}, new double[]{1, 2, 3}}, ClickHouseColumn.parse("v Array(Array(Float64))").get(0)}, + }; + } + + @Test + void testStringValues() throws SQLException { + String[] array = new String[] {"a", null, "c"}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Nullable(String))").get(0)); + + final String valueColumn = rs.getMetaData().getColumnLabel(2); + int len = java.lang.reflect.Array.getLength(array); + for (int i = 0; i < len; i++) { + rs.next(); + String value = array[i]; + assertEquals(rs.getString(valueColumn), value); + assertEquals(rs.getObject(valueColumn), value); + assertEquals(rs.getNString(valueColumn), value); + if (value == null) { + assertTrue(rs.wasNull()); + } else { + assertEquals(rs.getBytes(valueColumn), value.getBytes()); + } + } + } + + @Test + void testEmptyArray() throws SQLException { + ArrayResultSet rs = new ArrayResultSet(new Object[0], ClickHouseColumn.parse("v Array(Int32)").get(0)); + + Assert.assertTrue(rs.isBeforeFirst()); + Assert.assertFalse(rs.isAfterLast()); + Assert.assertFalse(rs.isLast()); + Assert.assertFalse(rs.isFirst()); + + assertFalse(rs.next()); + + Assert.assertTrue(rs.isBeforeFirst()); + Assert.assertFalse(rs.isAfterLast()); + Assert.assertFalse(rs.isLast()); + Assert.assertFalse(rs.isFirst()); + + Assert.assertThrows(SQLException.class, () -> rs.getString("col1")); + Assert.assertThrows(SQLException.class, () -> rs.getObject("col1")); + Assert.assertThrows(SQLException.class, () -> rs.getInt("col1")); + } + + @Test + public void testReadOnlyException() throws Throwable { + Integer[] array = {1, null, 3, 4, 5}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Int32)").get(0)); + + rs.next(); + final String valueColumn = rs.getMetaData().getColumnName(2); + Assert.ThrowingRunnable[] rsUnsupportedMethods = new Assert.ThrowingRunnable[]{ + rs::moveToCurrentRow, + rs::moveToInsertRow, + rs::refreshRow, + () -> rs.updateBoolean("col1", true), + () -> rs.updateByte("col1", (byte) 1), + () -> rs.updateShort("col1", (short) 1), + () -> rs.updateInt("col1", 1), + () -> rs.updateLong("col1", 1L), + () -> rs.updateFloat("col1", 1.1f), + () -> rs.updateDouble("col1", 1.1), + () -> rs.updateBigDecimal("col1", BigDecimal.valueOf(1.1)), + () -> rs.updateString("col1", "test"), + () -> rs.updateNString("col1", "test"), + () -> rs.updateBytes("col1", new byte[1]), + () -> rs.updateDate("col1", Date.valueOf("2020-01-01")), + () -> rs.updateTime("col1", Time.valueOf("12:34:56")), + () -> rs.updateTimestamp("col1", Timestamp.valueOf("2020-01-01 12:34:56.789123")), + () -> rs.updateBlob("col1", (Blob) null), + () -> rs.updateClob("col1", new StringReader("test")), + () -> rs.updateNClob("col1", new StringReader("test")), + () -> rs.updateBoolean(1, true), + () -> rs.updateByte(1, (byte) 1), + () -> rs.updateShort(1, (short) 1), + () -> rs.updateInt(1, 1), + () -> rs.updateLong(1, 1L), + () -> rs.updateFloat(1, 1.1f), + () -> rs.updateDouble(1, 1.1), + () -> rs.updateBigDecimal(1, BigDecimal.valueOf(1.1)), + () -> rs.updateString(1, "test"), + () -> rs.updateNString(1, "test"), + () -> rs.updateBytes(1, new byte[1]), + () -> rs.updateDate(1, Date.valueOf("2020-01-01")), + () -> rs.updateTime(1, Time.valueOf("12:34:56")), + () -> rs.updateTimestamp(1, Timestamp.valueOf("2020-01-01 12:34:56.789123")), + () -> rs.updateBlob(1, (Blob) null), + () -> rs.updateClob(1, new StringReader("test")), + () -> rs.updateNClob(1, new StringReader("test")), + () -> rs.updateSQLXML(1, null), + () -> rs.updateObject(1, 1), + () -> rs.updateObject("col1", 1), + () -> rs.updateObject(1, "test", Types.INTEGER), + () -> rs.updateObject("col1", "test", Types.INTEGER), + () -> rs.updateObject(1, "test", JDBCType.INTEGER), + () -> rs.updateObject("col1", "test", JDBCType.INTEGER), + () -> rs.updateObject(1, "test", JDBCType.INTEGER, 1), + () -> rs.updateCharacterStream(1, new StringReader("test"), 1), + () -> rs.updateCharacterStream("col1", new StringReader("test")), + () -> rs.updateCharacterStream("col1", new StringReader("test"), 1), + () -> rs.updateCharacterStream(1, new StringReader("test"), 1L), + () -> rs.updateCharacterStream("col1", new StringReader("test"), 1L), + () -> rs.updateCharacterStream(1, new StringReader("test")), + () -> rs.updateCharacterStream("col1", new StringReader("test")), + () -> rs.updateNCharacterStream(1, new StringReader("test"), 1), + () -> rs.updateNCharacterStream("col1", new StringReader("test"), 1), + () -> rs.updateNCharacterStream(1, new StringReader("test"), 1L), + () -> rs.updateNCharacterStream("col1", new StringReader("test"), 1L), + () -> rs.updateNCharacterStream(1, new StringReader("test")), + () -> rs.updateNCharacterStream("col1", new StringReader("test")), + () -> rs.updateBlob(1, (InputStream) null), + () -> rs.updateBlob("col1", (InputStream) null), + () -> rs.updateBlob(1, (InputStream) null, -1), + () -> rs.updateBlob("col1", (InputStream) null, -1), + () -> rs.updateBinaryStream(1, (InputStream) null), + () -> rs.updateBinaryStream("col1", (InputStream) null), + () -> rs.updateBinaryStream(1, (InputStream) null, -1), + () -> rs.updateBinaryStream("col1", (InputStream) null, -1), + () -> rs.updateBinaryStream(1, (InputStream) null, -1L), + () -> rs.updateBinaryStream("col1", (InputStream) null, -1L), + () -> rs.updateAsciiStream(1, (InputStream) null), + () -> rs.updateAsciiStream("col1", (InputStream) null), + () -> rs.updateAsciiStream(1, (InputStream) null, -1), + () -> rs.updateAsciiStream("col1", (InputStream) null, -1), + () -> rs.updateAsciiStream(1, (InputStream) null, -1L), + () -> rs.updateAsciiStream("col1", (InputStream) null, -1L), + () -> rs.updateClob(1, (Reader) null), + () -> rs.updateClob("col1", (Reader) null), + () -> rs.updateClob(1, (Reader) null, -1), + () -> rs.updateClob("col1", (Reader) null, -1), + () -> rs.updateClob(1, (Reader) null, -1L), + () -> rs.updateClob("col1", (Reader) null, -1L), + () -> rs.updateNClob(1, (Reader) null), + () -> rs.updateNClob("col1", (Reader) null), + () -> rs.updateNClob(1, (NClob) null), + () -> rs.updateNClob("col1", (NClob) null), + () -> rs.updateNClob(1, (Reader) null, -1), + () -> rs.updateNClob("col1", (Reader) null, -1), + () -> rs.updateNClob(1, (Reader) null, -1L), + () -> rs.updateNClob("col1", (Reader) null, -1L), + () -> rs.updateRef(1, (Ref) null), + () -> rs.updateRef("col1", (Ref) null), + () -> rs.updateArray(1, (java.sql.Array) null), + () -> rs.updateArray("col1", (java.sql.Array) null), + rs::cancelRowUpdates, + () -> rs.updateNull(1), + () -> rs.updateNull("col1"), + () -> rs.updateRowId(1, null), + () -> rs.updateRowId("col1", null), + () -> rs.updateClob(1, (Clob) null), + () -> rs.updateClob("col1", (Clob) null), + rs::updateRow, + rs::insertRow, + rs::deleteRow, + () -> rs.getCharacterStream(valueColumn), + () -> rs.getBinaryStream(valueColumn), + () -> rs.getUnicodeStream(valueColumn), + () -> rs.getAsciiStream(valueColumn), + () -> rs.getNCharacterStream(valueColumn), + () -> rs.getNClob(valueColumn), + () -> rs.getClob(valueColumn), + () -> rs.getBlob(valueColumn), + () -> rs.getSQLXML(valueColumn), + () -> rs.getRef(valueColumn), + () -> rs.getRowId(valueColumn), + () -> rs.getSQLXML(valueColumn), + }; + + for (Assert.ThrowingRunnable op : rsUnsupportedMethods) { + Assert.assertThrows(SQLException.class, op); + } + } + + @Test + void testIndexColumn() throws Exception { + Integer[] array = {1, null, 3, 4, 5}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Int32)").get(0)); + + final String indexColumn = rs.getMetaData().getColumnName(1); + Assert.assertEquals(indexColumn, "INDEX"); + rs.next(); + assertEquals(rs.getObject(indexColumn), 1); + assertEquals(rs.getObject(indexColumn, String.class), "1"); + assertEquals(rs.getObject(indexColumn, Long.class), 1L); + assertEquals(rs.getObject(indexColumn, Integer.class), 1); + assertEquals(rs.getObject(indexColumn, Short.class), (short) 1); + assertEquals(rs.getObject(indexColumn, Byte.class), (byte) 1); + } + + @Test + void testDateColumn() throws Exception { + Date[] array = {Date.valueOf("2020-01-01"), null, Date.valueOf("2020-01-02")}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Date)").get(0)); + + final String dateColumn = rs.getMetaData().getColumnName(2); + Assert.assertEquals(dateColumn, "VALUE"); + rs.next(); + assertEquals(rs.getObject(dateColumn), Date.valueOf("2020-01-01")); + assertEquals(rs.getObject(dateColumn, String.class), "2020-01-01"); + assertEquals(rs.getDate(dateColumn), rs.getObject(dateColumn, Date.class)); + assertEquals(rs.getDate(dateColumn, new GregorianCalendar()), rs.getObject(dateColumn, Date.class)); + } + + @Test + void testTimeColumn() throws Exception { + Time[] array = {Time.valueOf("12:34:56"), null, Time.valueOf("12:34:57")}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Time)").get(0)); + + final String timeColumn = rs.getMetaData().getColumnName(2); + Assert.assertEquals(timeColumn, "VALUE"); + rs.next(); + assertEquals(rs.getObject(timeColumn), Time.valueOf("12:34:56")); + assertEquals(rs.getObject(timeColumn, String.class), "12:34:56"); + assertEquals(rs.getTime(timeColumn), rs.getObject(timeColumn, Time.class)); + assertEquals(rs.getTime(timeColumn, new GregorianCalendar()), rs.getObject(timeColumn, Time.class)); + } + + @Test + void testTimestampColumn() throws Exception { + Timestamp[] array = {Timestamp.valueOf("2020-01-01 12:34:56.789123"), null, Timestamp.valueOf("2020-01-01 12:34:56.789124")}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Timestamp)").get(0)); + + final String timestampColumn = rs.getMetaData().getColumnName(2); + Assert.assertEquals(timestampColumn, "VALUE"); + rs.next(); + assertEquals(rs.getObject(timestampColumn), Timestamp.valueOf("2020-01-01 12:34:56.789123")); + assertEquals(rs.getObject(timestampColumn, String.class), "2020-01-01 12:34:56.789123"); + assertEquals(rs.getTimestamp(timestampColumn), rs.getObject(timestampColumn, Timestamp.class)); + assertEquals(rs.getTimestamp(timestampColumn, new GregorianCalendar()), rs.getObject(timestampColumn, Timestamp.class)); + } + + @Test + void testStringColumn() throws Exception { + String[] array = {"123", null, "456"}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(String)").get(0)); + + final String stringColumn = rs.getMetaData().getColumnName(2); + Assert.assertEquals(stringColumn, "VALUE"); + rs.next(); + assertEquals(rs.getObject(stringColumn), "123"); + assertEquals(rs.getObject(stringColumn, String.class), "123"); + assertEquals(rs.getString(stringColumn), rs.getObject(stringColumn, String.class)); + assertEquals(rs.getByte(stringColumn), 123); + assertEquals(rs.getShort(stringColumn), (short) 123); + assertEquals(rs.getInt(stringColumn), 123); + assertEquals(rs.getLong(stringColumn), 123L); + assertEquals(rs.getFloat(stringColumn), 123.0f); + assertEquals(rs.getDouble(stringColumn), 123.0); + } + + @Test + void testStringToURL() throws Exception { + String[] array = {"http://test.com", null, "https://test.com"}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(String)").get(0)); + + final String stringColumn = rs.getMetaData().getColumnName(2); + Assert.assertEquals(stringColumn, "VALUE"); + rs.next(); + assertEquals(rs.getObject(stringColumn), "http://test.com"); + assertEquals(rs.getObject(stringColumn, String.class), "http://test.com"); + assertEquals(rs.getString(stringColumn), rs.getObject(stringColumn, String.class)); + assertEquals(rs.getURL(stringColumn), rs.getObject(stringColumn, URL.class)); + } + + @Test + void testInvalidStringConverts() throws Exception { + String[] array = {"abc"}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(String)").get(0)); + + final String stringColumn = rs.getMetaData().getColumnName(2); + rs.next(); + Assert.assertThrows(SQLException.class, () -> rs.getByte(stringColumn)); + Assert.assertThrows(SQLException.class, () -> rs.getShort(stringColumn)); + Assert.assertThrows(SQLException.class, () -> rs.getInt(stringColumn)); + Assert.assertThrows(SQLException.class, () -> rs.getLong(stringColumn)); + Assert.assertThrows(SQLException.class, () -> rs.getFloat(stringColumn)); + Assert.assertThrows(SQLException.class, () -> rs.getDouble(stringColumn)); + } + + @Test + void testArrayOfObjects() throws Exception { + Object[] array = {null, 2, 3}; + ArrayResultSet rs = new ArrayResultSet(array, ClickHouseColumn.parse("v Array(Nullable(UInt32))").get(0)); + + final String valueColumn = rs.getMetaData().getColumnName(2); + rs.next(); + assertEquals(rs.getObject(valueColumn), null); + assertTrue(rs.wasNull()); + + rs.next(); + assertEquals(rs.getInt(valueColumn), 2); + assertEquals(rs.getObject(valueColumn, String.class), "2"); + assertEquals(rs.getString(valueColumn), rs.getObject(valueColumn, String.class)); + } +} \ No newline at end of file