diff --git a/README.md b/README.md index 16d605c8d..6472dc75d 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ Java 8 or higher is required in order to use Java client([clickhouse-client](htt com.clickhouse clickhouse-http-client - 0.3.2-patch2 + 0.3.2-patch3 ``` @@ -135,7 +135,7 @@ try (ClickHouseClient client = ClickHouseClient.newInstance(preferredProtocol); com.clickhouse clickhouse-jdbc - 0.3.2-patch2 + 0.3.2-patch3 http diff --git a/clickhouse-client/README.md b/clickhouse-client/README.md index 3e62dfab4..9f465c47e 100644 --- a/clickhouse-client/README.md +++ b/clickhouse-client/README.md @@ -9,7 +9,7 @@ Async Java client for ClickHouse. `clickhouse-client` is an abstract module, so com.clickhouse clickhouse-http-client - 0.3.2-patch1 + 0.3.2-patch3 ``` diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java index ba3e875e1..d089b9bf9 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseColumn.java @@ -45,6 +45,7 @@ public final class ClickHouseColumn implements Serializable { private static ClickHouseColumn update(ClickHouseColumn column) { column.enumConstants = ClickHouseEnum.EMPTY; int size = column.parameters.size(); + column.precision = column.dataType.getMaxPrecision(); switch (column.dataType) { case Array: column.arrayLevel = 1; diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java index 1c23f285a..c748fbd74 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseDataType.java @@ -44,15 +44,15 @@ public enum ClickHouseDataType { UInt32(Long.class, false, true, false, 4, 10, 0, 0, 0, "INT UNSIGNED", "INTEGER UNSIGNED", "MEDIUMINT UNSIGNED"), UInt64(Long.class, false, true, false, 8, 20, 0, 0, 0, "BIGINT UNSIGNED"), UInt128(BigInteger.class, false, true, false, 16, 39, 0, 0, 0), - UInt256(BigInteger.class, false, true, false, 32, 78, 0, 0, 0), Int8(Byte.class, false, true, true, 1, 3, 0, 0, 0, - "BYTE", "INT1", "INT1 SIGNED", "TINYINT", "TINYINT SIGNED"), + UInt256(BigInteger.class, false, true, false, 32, 78, 0, 0, 0), + Int8(Byte.class, false, true, true, 1, 3, 0, 0, 0, "BYTE", "INT1", "INT1 SIGNED", "TINYINT", "TINYINT SIGNED"), Int16(Short.class, false, true, true, 2, 5, 0, 0, 0, "SMALLINT", "SMALLINT SIGNED"), Int32(Integer.class, false, true, true, 4, 10, 0, 0, 0, "INT", "INTEGER", "MEDIUMINT", "INT SIGNED", "INTEGER SIGNED", "MEDIUMINT SIGNED"), Int64(Long.class, false, true, true, 8, 19, 0, 0, 0, "BIGINT", "BIGINT SIGNED"), Int128(BigInteger.class, false, true, true, 16, 39, 0, 0, 0), Int256(BigInteger.class, false, true, true, 32, 77, 0, 0, 0), - Bool(Boolean.class, false, false, true, 1, 3, 0, 0, 0, "BOOLEAN"), + Bool(Boolean.class, false, false, true, 1, 1, 0, 0, 0, "BOOLEAN"), Date(LocalDate.class, false, false, false, 2, 10, 0, 0, 0), Date32(LocalDate.class, false, false, false, 4, 10, 0, 0, 0), DateTime(LocalDateTime.class, true, false, false, 0, 29, 0, 0, 9, "TIMESTAMP"), @@ -63,22 +63,28 @@ public enum ClickHouseDataType { Decimal64(BigDecimal.class, true, false, true, 8, 18, 18, 0, 18), Decimal128(BigDecimal.class, true, false, true, 16, 38, 38, 0, 38), Decimal256(BigDecimal.class, true, false, true, 32, 76, 20, 0, 76), - UUID(UUID.class, false, true, false, 16, 69, 0, 0, 0), Enum(String.class, true, true, false, 1, 0, 0, 0, 0), - Enum8(String.class, true, true, false, 1, 0, 0, 0, 0), Enum16(String.class, true, true, false, 2, 0, 0, 0, 0), + UUID(UUID.class, false, true, false, 16, 69, 0, 0, 0), + @Deprecated + Enum(String.class, true, true, false, 1, 0, 0, 0, 0), + Enum8(String.class, true, true, false, 1, 0, 0, 0, 0), // "ENUM"), + Enum16(String.class, true, true, false, 2, 0, 0, 0, 0), Float32(Float.class, false, true, true, 4, 12, 0, 0, 38, "FLOAT", "REAL", "SINGLE"), Float64(Double.class, false, true, true, 16, 22, 0, 0, 308, "DOUBLE", "DOUBLE PRECISION"), - IPv4(Inet4Address.class, false, true, false, 4, 0, 0, 0, 0, "INET4"), - IPv6(Inet6Address.class, false, true, false, 16, 0, 0, 0, 0, "INET6"), + IPv4(Inet4Address.class, false, true, false, 4, 10, 0, 0, 0, "INET4"), + IPv6(Inet6Address.class, false, true, false, 16, 39, 0, 0, 0, "INET6"), FixedString(String.class, true, true, false, 0, 0, 0, 0, 0, "BINARY"), String(String.class, false, true, false, 0, 0, 0, 0, 0, "BINARY LARGE OBJECT", "BINARY VARYING", "BLOB", "BYTEA", - "CHAR", "CHAR LARGE OBJECT", "CHAR VARYING", "CHARACTER", "CHARACTER LARGE OBJECT", "CHARACTER VARYING", - "CLOB", "LONGBLOB", "LONGTEXT", "MEDIUMBLOB", "MEDIUMTEXT", "NATIONAL CHAR", "NATIONAL CHAR VARYING", - "NATIONAL CHARACTER", "NATIONAL CHARACTER LARGE OBJECT", "NATIONAL CHARACTER VARYING", "NCHAR", - "NCHAR LARGE OBJECT", "NCHAR VARYING", "NVARCHAR", "TEXT", "TINYBLOB", "TINYTEXT", "VARCHAR", "VARCHAR2"), + "CHAR", "CHARACTER", "CHARACTER LARGE OBJECT", "CHARACTER VARYING", "CHAR LARGE OBJECT", "CHAR VARYING", + "CLOB", "LONGBLOB", "LONGTEXT", "MEDIUMBLOB", "MEDIUMTEXT", "NATIONAL CHAR", "NATIONAL CHARACTER", + "NATIONAL CHARACTER LARGE OBJECT", "NATIONAL CHARACTER VARYING", "NATIONAL CHAR VARYING", "NCHAR", + "NCHAR LARGE OBJECT", "NCHAR VARYING", "NVARCHAR", "TEXT", "TINYBLOB", "TINYTEXT", "VARBINARY", "VARCHAR", + "VARCHAR2"), AggregateFunction(String.class, true, true, false, 0, 0, 0, 0, 0), // implementation-defined intermediate state SimpleAggregateFunction(String.class, true, true, false, 0, 0, 0, 0, 0), - Array(Object.class, true, true, false, 0, 0, 0, 0, 0), Map(Map.class, true, true, false, 0, 0, 0, 0, 0), - Nested(Object.class, true, true, false, 0, 0, 0, 0, 0), Tuple(List.class, true, true, false, 0, 0, 0, 0, 0), + Array(Object.class, true, true, false, 0, 0, 0, 0, 0), + Map(Map.class, true, true, false, 0, 0, 0, 0, 0), + Nested(Object.class, true, true, false, 0, 0, 0, 0, 0), + Tuple(List.class, true, true, false, 0, 0, 0, 0, 0), Point(Object.class, false, true, true, 33, 0, 0, 0, 0), // same as Tuple(Float64, Float64) Polygon(Object.class, false, true, true, 0, 0, 0, 0, 0), // same as Array(Ring) MultiPolygon(Object.class, false, true, true, 0, 0, 0, 0, 0), // same as Array(Polygon) diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseInputStream.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseInputStream.java index e5e6d1c64..2e07935b4 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseInputStream.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseInputStream.java @@ -298,7 +298,6 @@ public byte[] readBytes(int length) throws IOException { ensureOpen(); byte[] bytes = new byte[length]; - int offset = 0; int counter = 0; while (counter < length) { if (position >= limit && updateBuffer() < 0) { @@ -314,9 +313,8 @@ public byte[] readBytes(int length) throws IOException { } int size = Math.min(limit - position, length - counter); - System.arraycopy(buffer, position, bytes, offset, size); + System.arraycopy(buffer, position, bytes, counter, size); position += size; - offset += size; counter += size; } diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java index 64b5b6cec..c3ee8d675 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValue.java @@ -538,6 +538,55 @@ default > T asObject(Class clazz) { } } + /** + * Gets binary value as byte array. + * + * @return byte array which could be null + */ + default byte[] asBinary() { + return asBinary(0, null); + } + + /** + * Gets binary value as fixed length byte array. + * + * @param length byte length of value, 0 or negative number means no limit + * @return byte array which could be null + */ + default byte[] asBinary(int length) { + return asBinary(length, null); + } + + /** + * Gets binary value as byte array. + * + * @param charset charset, null is same as default(UTF-8) + * @return byte array which could be null + */ + default byte[] asBinary(Charset charset) { + return asBinary(0, charset); + } + + /** + * Gets binary value as byte array. + * + * @param length byte length of value, 0 or negative number means no limit + * @param charset charset, null is same as default(UTF-8) + * @return byte array which could be null + */ + default byte[] asBinary(int length, Charset charset) { + if (isNullOrEmpty()) { + return null; + } + + byte[] bytes = asString().getBytes(charset == null ? StandardCharsets.UTF_8 : charset); + if (length > 0) { + ClickHouseChecker.notWithDifferentLength(bytes, length); + } + + return bytes; + } + /** * Gets value as unbounded string, using default charset(usually UTF-8). * diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java index afb40a934..f0a9df3d5 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseValues.java @@ -8,6 +8,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -76,7 +77,9 @@ public final class ClickHouseValues { public static final long[] EMPTY_LONG_ARRAY = new long[0]; public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; + public static final String EMPTY_ARRAY_EXPR = "[]"; + public static final String EMPTY_STRING_EXPR = "''"; public static final BigDecimal NANOS = new BigDecimal(BigInteger.TEN.pow(9)); @@ -134,6 +137,10 @@ public final class ClickHouseValues { public static final String TYPE_CLASS = "Class"; + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + private static final byte[] UNHEX_PREFIX = "unhex('".getBytes(StandardCharsets.US_ASCII); + private static final byte[] UNHEX_SUFFIX = "')".getBytes(StandardCharsets.US_ASCII); + /** * Converts IP address to big integer. * @@ -180,6 +187,52 @@ public static BigInteger convertToBigInteger(UUID value) { return low.add(high.multiply(BIGINT_HL_BOUNDARY)); } + /** + * Converts given byte array to string in hexadecimal format. + * + * @param bytes byte array + * @return non-null string + */ + public static String convertToHexString(byte[] bytes) { + int len = bytes != null ? bytes.length : 0; + if (len == 0) { + return ""; + } + + byte[] hexChars = new byte[len * 2]; + for (int i = 0; i < len; i++) { + int v = bytes[i] & 0xFF; + int j = i * 2; + hexChars[j] = HEX_ARRAY[v >>> 4]; + hexChars[j + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } + + /** + * Converts given byte array to unhex() expression. + * + * @param bytes byte array + * @return non-null expression + */ + public static String convertToUnhexExpression(byte[] bytes) { + int len = bytes != null ? bytes.length : 0; + if (len == 0) { + return EMPTY_STRING_EXPR; + } + + int offset = UNHEX_PREFIX.length; + byte[] hexChars = new byte[len * 2 + offset + UNHEX_SUFFIX.length]; + System.arraycopy(UNHEX_PREFIX, 0, hexChars, 0, offset); + System.arraycopy(UNHEX_SUFFIX, 0, hexChars, hexChars.length - UNHEX_SUFFIX.length, UNHEX_SUFFIX.length); + for (int i = 0; i < len; i++) { + int v = bytes[i] & 0xFF; + hexChars[offset++] = HEX_ARRAY[v >>> 4]; + hexChars[offset++] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } + /** * Converts big decimal to instant. * diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java index cc22ce3d0..9ba394037 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/BinaryStreamUtils.java @@ -1649,6 +1649,19 @@ public static void writeString(OutputStream output, String value, Charset charse output.write(bytes); } + /** + * Writes a binary string to given output stream. + * + * @param output non-null output stream + * @param value non-null byte array + * @throws IOException when failed to write value to output stream or reached + * end of the stream + */ + public static void writeString(OutputStream output, byte[] value) throws IOException { + writeVarInt(output, value.length); + output.write(value); + } + /** * Read varint from given input stream. * diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java index 75017a081..f8ce0646f 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseRowBinaryProcessor.java @@ -331,13 +331,11 @@ private void buildMappingsForDataTypes() { // string and uuid buildMappings(deserializers, serializers, - (r, f, c, i) -> ClickHouseStringValue.of(r, BinaryStreamUtils.readFixedString(i, c.getPrecision())), - (v, f, c, o) -> BinaryStreamUtils.writeFixedString(o, v.asString(c.getPrecision()), - c.getPrecision()), - ClickHouseDataType.FixedString); + (r, f, c, i) -> ClickHouseStringValue.of(r, i.readBytes(c.getPrecision())), + (v, f, c, o) -> o.write(v.asBinary(c.getPrecision())), ClickHouseDataType.FixedString); buildMappings(deserializers, serializers, - (r, f, c, i) -> ClickHouseStringValue.of(r, i.readUnicodeString()), - (v, f, c, o) -> BinaryStreamUtils.writeString(o, v.asString()), ClickHouseDataType.String); + (r, f, c, i) -> ClickHouseStringValue.of(r, i.readBytes(i.readVarInt())), + (v, f, c, o) -> BinaryStreamUtils.writeString(o, v.asBinary()), ClickHouseDataType.String); buildMappings(deserializers, serializers, (r, f, c, i) -> ClickHouseUuidValue.of(r, BinaryStreamUtils.readUuid(i)), (v, f, c, o) -> BinaryStreamUtils.writeUuid(o, v.asUuid()), ClickHouseDataType.UUID); diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java index 7e65e817d..06435d419 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/ClickHouseStringValue.java @@ -9,6 +9,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.Arrays; import java.util.Objects; import java.util.UUID; import com.clickhouse.client.ClickHouseChecker; @@ -35,8 +36,8 @@ public static ClickHouseStringValue ofNull() { * @return same object as {@code ref} or a new instance if it's null */ public static ClickHouseStringValue ofNull(ClickHouseValue ref) { - return ref instanceof ClickHouseStringValue ? ((ClickHouseStringValue) ref).set(null) - : new ClickHouseStringValue(null); + return ref instanceof ClickHouseStringValue ? ((ClickHouseStringValue) ref).set((String) null) + : new ClickHouseStringValue((String) null); } /** @@ -49,6 +50,16 @@ public static ClickHouseStringValue of(String value) { return of(null, value); } + /** + * Wrap the given value. + * + * @param bytes bytes + * @return object representing the value + */ + public static ClickHouseStringValue of(byte[] bytes) { + return of(null, bytes); + } + /** * Update value of the given object or create a new instance if {@code ref} is * null. @@ -62,75 +73,103 @@ public static ClickHouseStringValue of(ClickHouseValue ref, String value) { : new ClickHouseStringValue(value); } + /** + * Update value of the given object or create a new instance if {@code ref} is + * null. + * + * @param ref object to update, could be null + * @param bytes bytes + * @return same object as {@code ref} or a new instance if it's null + */ + public static ClickHouseStringValue of(ClickHouseValue ref, byte[] bytes) { + return ref instanceof ClickHouseStringValue ? ((ClickHouseStringValue) ref).set(bytes) + : new ClickHouseStringValue(bytes); + } + + private boolean binary; + private byte[] bytes; private String value; protected ClickHouseStringValue(String value) { update(value); } + protected ClickHouseStringValue(byte[] bytes) { + update(bytes); + } + protected ClickHouseStringValue set(String value) { + this.binary = false; + this.bytes = null; this.value = value; return this; } - /** - * Gets value. - * - * @return value - */ - public String getValue() { - return value; + protected ClickHouseStringValue set(byte[] bytes) { + this.binary = true; + this.bytes = bytes; + this.value = null; + return this; } @Override public ClickHouseStringValue copy(boolean deep) { - return new ClickHouseStringValue(value); + if (bytes == null || !binary) { + return new ClickHouseStringValue(value); + } + + byte[] b = bytes; + if (deep) { + b = new byte[bytes.length]; + System.arraycopy(bytes, 0, b, 0, bytes.length); + } + return new ClickHouseStringValue(b); } @Override public boolean isNullOrEmpty() { - return value == null; + return bytes == null && value == null; } @Override public boolean asBoolean() { // what about Y/N, Yes/No, enabled/disabled? - return !isNullOrEmpty() && Boolean.parseBoolean(value); + return !isNullOrEmpty() && Boolean.parseBoolean(asString()); } @Override public byte asByte() { - return isNullOrEmpty() ? (byte) 0 : Byte.parseByte(value); + return isNullOrEmpty() ? (byte) 0 : Byte.parseByte(asString()); } @Override public short asShort() { - return isNullOrEmpty() ? (short) 0 : Short.parseShort(value); + return isNullOrEmpty() ? (short) 0 : Short.parseShort(asString()); } @Override public int asInteger() { - return isNullOrEmpty() ? 0 : Integer.parseInt(value); + return isNullOrEmpty() ? 0 : Integer.parseInt(asString()); } @Override public long asLong() { - return isNullOrEmpty() ? 0L : Long.parseLong(value); + return isNullOrEmpty() ? 0L : Long.parseLong(asString()); } @Override public BigInteger asBigInteger() { - return isNullOrEmpty() ? null : new BigInteger(value); + return isNullOrEmpty() ? null : new BigInteger(asString()); } @Override public float asFloat() { - return isNullOrEmpty() ? 0F : Float.parseFloat(value); + return isNullOrEmpty() ? 0F : Float.parseFloat(asString()); } @Override public double asDouble() { - return isNullOrEmpty() ? 0D : Double.parseDouble(value); + return isNullOrEmpty() ? 0D : Double.parseDouble(asString()); } @Override @@ -140,49 +179,77 @@ public BigDecimal asBigDecimal(int scale) { @Override public LocalDate asDate() { - return isNullOrEmpty() ? null : LocalDate.parse(value, ClickHouseValues.DATE_FORMATTER); + return isNullOrEmpty() ? null : LocalDate.parse(asString(), ClickHouseValues.DATE_FORMATTER); } @Override public LocalTime asTime() { - return isNullOrEmpty() ? null : LocalTime.parse(value, ClickHouseValues.TIME_FORMATTER); + return isNullOrEmpty() ? null : LocalTime.parse(asString(), ClickHouseValues.TIME_FORMATTER); } @Override public LocalDateTime asDateTime(int scale) { - return isNullOrEmpty() ? null : LocalDateTime.parse(value, ClickHouseValues.DATETIME_FORMATTER); + return isNullOrEmpty() ? null : LocalDateTime.parse(asString(), ClickHouseValues.DATETIME_FORMATTER); } @Override public > T asEnum(Class enumType) { - return isNullOrEmpty() ? null : Enum.valueOf(enumType, value); + return isNullOrEmpty() ? null : Enum.valueOf(enumType, asString()); } @Override public Inet4Address asInet4Address() { - return ClickHouseValues.convertToIpv4(getValue()); + return ClickHouseValues.convertToIpv4(asString()); } @Override public Inet6Address asInet6Address() { - return ClickHouseValues.convertToIpv6(getValue()); + return ClickHouseValues.convertToIpv6(asString()); } @Override public Object asObject() { - return value; + return asString(); // bytes != null ? bytes : value; + } + + @Override + public byte[] asBinary() { + if (value != null && bytes == null) { + bytes = value.getBytes(StandardCharsets.UTF_8); + } + + return bytes; + } + + @Override + public byte[] asBinary(int length, Charset charset) { + if (value != null && bytes == null) { + bytes = value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset); + } + + if (bytes != null && length > 0) { + return ClickHouseChecker.notWithDifferentLength(bytes, length); + } else { + return bytes; + } } @Override public String asString() { + if (bytes != null && value == null) { + value = new String(bytes, StandardCharsets.UTF_8); + } + return value; } @Override public String asString(int length, Charset charset) { if (value != null && length > 0) { - ClickHouseChecker.notWithDifferentLength(value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset), - length); + if (bytes == null) { + bytes = value.getBytes(charset == null ? StandardCharsets.UTF_8 : charset); + } + ClickHouseChecker.notWithDifferentLength(bytes, length); } return value; @@ -190,17 +257,22 @@ public String asString(int length, Charset charset) { @Override public UUID asUuid() { - return isNullOrEmpty() ? null : UUID.fromString(value); + return isNullOrEmpty() ? null : UUID.fromString(asString()); } @Override public ClickHouseStringValue resetToNullOrEmpty() { - return set(null); + return set((String) null); } @Override public String toSqlExpression() { - return ClickHouseValues.convertToQuotedString(value); + if (isNullOrEmpty()) { + return ClickHouseValues.NULL_EXPR; + } else if (binary) { + return ClickHouseValues.convertToUnhexExpression(bytes); + } + return ClickHouseValues.convertToQuotedString(asString()); } @Override @@ -219,6 +291,11 @@ public ClickHouseStringValue update(byte value) { return set(String.valueOf(value)); } + @Override + public ClickHouseStringValue update(byte[] value) { + return set(value); + } + @Override public ClickHouseStringValue update(short value) { return set(String.valueOf(value)); @@ -304,6 +381,16 @@ public ClickHouseStringValue update(Object value) { return update(value == null ? null : value.toString()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (binary ? 1231 : 1237); + result = prime * result + Arrays.hashCode(bytes); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + @Override public boolean equals(Object obj) { if (this == obj) { // too bad this is a mutable class :< @@ -313,12 +400,7 @@ public boolean equals(Object obj) { } ClickHouseStringValue v = (ClickHouseStringValue) obj; - return value == v.value || (value != null && value.equals(v.value)); - } - - @Override - public int hashCode() { - return Objects.hash(value); + return binary == v.binary && Objects.equals(bytes, v.bytes) && Objects.equals(value, v.value); } @Override diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java index 8c44cd6bd..d80e2dde4 100644 --- a/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java +++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/ClickHouseStringValueTest.java @@ -24,15 +24,15 @@ public void testInitiation() { String value = null; ClickHouseStringValue v = ClickHouseStringValue.of(null, value); Assert.assertEquals(v.asString(), value); - Assert.assertEquals(v.getValue(), value); + Assert.assertEquals(v.asObject(), value); v = ClickHouseStringValue.of(null, value = ""); Assert.assertEquals(v.asString(), value); - Assert.assertEquals(v.getValue(), value); + Assert.assertEquals(v.asObject(), value); v = ClickHouseStringValue.of(null, value = "123"); Assert.assertEquals(v.asString(), value); - Assert.assertEquals(v.getValue(), value); + Assert.assertEquals(v.asObject(), value); // same instance but different value Assert.assertEquals(v, v.update("321")); @@ -55,6 +55,27 @@ public void testTypeConversion() { LocalDateTime.of(2021, 3, 4, 15, 6, 27, 123456789)); } + @Test(groups = { "unit" }) + public void testBinaryValue() { + Assert.assertEquals(ClickHouseStringValue.of((byte[]) null).asBinary(), null); + Assert.assertEquals(ClickHouseStringValue.of((String) null).asBinary(), null); + Assert.assertEquals(ClickHouseStringValue.of(new byte[0]).asBinary(), new byte[0]); + Assert.assertEquals(ClickHouseStringValue.of("").asBinary(), new byte[0]); + Assert.assertEquals(ClickHouseStringValue.of((byte[]) null).asBinary(0), null); + Assert.assertEquals(ClickHouseStringValue.of((String) null).asBinary(0), null); + Assert.assertEquals(ClickHouseStringValue.of(new byte[0]).asBinary(0), new byte[0]); + Assert.assertEquals(ClickHouseStringValue.of("").asBinary(0), new byte[0]); + + Assert.assertThrows(IllegalArgumentException.class, () -> ClickHouseStringValue.of("").asBinary(1)); + + Assert.assertEquals(ClickHouseStringValue.of("a").asBinary(1), new byte[] { 97 }); + Assert.assertEquals(ClickHouseStringValue.of("a").asBinary(0), new byte[] { 97 }); + Assert.assertEquals(ClickHouseStringValue.of("a").asBinary(), new byte[] { 97 }); + + Assert.assertEquals(ClickHouseStringValue.of(new byte[0]).toSqlExpression(), "''"); + Assert.assertEquals(ClickHouseStringValue.of(new byte[] { 97, 98, 99 }).toSqlExpression(), "unhex('616263')"); + } + @Test(groups = { "unit" }) public void testValue() throws Exception { // null value diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java index 55149ee55..09c9f52b5 100644 --- a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java +++ b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java @@ -19,6 +19,7 @@ import com.clickhouse.client.ClickHouseResponse; import com.clickhouse.client.ClickHouseResponseSummary; import com.clickhouse.client.ClickHouseUtils; +import com.clickhouse.client.ClickHouseVersion; import com.clickhouse.client.config.ClickHouseClientOption; import com.clickhouse.client.data.ClickHouseExternalTable; import com.clickhouse.client.data.ClickHouseStringValue; @@ -143,6 +144,12 @@ public void testLogComment() throws Exception { String uuid = UUID.randomUUID().toString(); try (ClickHouseClient client = ClickHouseClient.newInstance()) { ClickHouseRequest request = client.connect(server).format(ClickHouseFormat.RowBinaryWithNamesAndTypes); + try (ClickHouseResponse resp = request + .query("select version()").execute().get()) { + if (!ClickHouseVersion.of(resp.firstRecord().getValue(0).asString()).check("[21.2,)")) { + return; + } + } try (ClickHouseResponse resp = request .option(ClickHouseClientOption.LOG_LEADING_COMMENT, true) .query("-- select something\r\nselect 1", uuid).execute().get()) { diff --git a/clickhouse-jdbc/README.md b/clickhouse-jdbc/README.md index 8f60af57a..0512cbe0c 100644 --- a/clickhouse-jdbc/README.md +++ b/clickhouse-jdbc/README.md @@ -11,7 +11,7 @@ Keep in mind that `clickhouse-jdbc` is synchronous, and in general it has more o com.clickhouse clickhouse-jdbc - 0.3.2-patch2 + 0.3.2-patch3 ``` diff --git a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaData.java b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaData.java index ab22d9ab7..781ec83f1 100644 --- a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaData.java +++ b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaData.java @@ -6,8 +6,12 @@ import java.sql.RowIdLifetime; import java.sql.SQLException; import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.Temporal; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -814,9 +818,9 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa String sql = ClickHouseParameterizedQuery .apply("select null as TABLE_CAT, database as TABLE_SCHEM, table as TABLE_NAME, " + "name as COLUMN_NAME, toInt32(:defaultType) as DATA_TYPE, type as TYPE_NAME, toInt32(0) as COLUMN_SIZE, " - + "0 as BUFFER_LENGTH, toInt32(null) as DECIMAL_DIGITS, 10 as NUM_PREC_RADIX, " + + "0 as BUFFER_LENGTH, cast(null as Nullable(Int32)) as DECIMAL_DIGITS, 10 as NUM_PREC_RADIX, " + "toInt32(position(type, 'Nullable(') >= 1 ? :defaultNullable : :defaultNonNull) as NULLABLE, :comment as REMARKS, default_expression as COLUMN_DEF, " - + "0 as SQL_DATA_TYPE, 0 as SQL_DATETIME_SUB, toInt32(null) as CHAR_OCTET_LENGTH, position as ORDINAL_POSITION, " + + "0 as SQL_DATA_TYPE, 0 as SQL_DATETIME_SUB, cast(null as Nullable(Int32)) as CHAR_OCTET_LENGTH, position as ORDINAL_POSITION, " + "position(type, 'Nullable(') >= 1 ? 'YES' : 'NO' as IS_NULLABLE, null as SCOPE_CATALOG, null as SCOPE_SCHEMA, null as SCOPE_TABLE, " + "null as SOURCE_DATA_TYPE, 'NO' as IS_AUTOINCREMENT, 'NO' as IS_GENERATEDCOLUMN from system.columns\n" + "where database like :database and table like :table and name like :column", params); @@ -825,7 +829,8 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa try { ClickHouseColumn column = ClickHouseColumn.of("", typeName); r.getValue("DATA_TYPE").update(JdbcTypeMapping.toJdbcType(typeMaps, column)); - r.getValue("COLUMN_SIZE").update(column.getDataType().getByteLength()); + r.getValue("COLUMN_SIZE").update( + column.getPrecision() > 0 ? column.getPrecision() : column.getDataType().getByteLength()); if (column.isNullable()) { r.getValue("NULLABLE").update(DatabaseMetaData.typeNullable); r.getValue("IS_NULLABLE").update("YES"); @@ -838,7 +843,9 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa r.getValue("CHAR_OCTET_LENGTH").update(column.getPrecision()); } - if (column.getScale() > 0) { + Class clazz = column.getDataType().getObjectClass(); + if (column.getScale() > 0 || Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) + || Temporal.class.isAssignableFrom(clazz)) { r.getValue("DECIMAL_DIGITS").update(column.getScale()); } else { r.getValue("DECIMAL_DIGITS").resetToNullOrEmpty(); diff --git a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseResultSet.java b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseResultSet.java index ad0726041..680d2c9b3 100644 --- a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseResultSet.java +++ b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/ClickHouseResultSet.java @@ -1,12 +1,15 @@ package com.clickhouse.jdbc; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Reader; +import java.io.StringReader; import java.io.UncheckedIOException; import java.math.BigDecimal; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; @@ -217,14 +220,13 @@ public Array getArray(String columnLabel) throws SQLException { @Override public InputStream getAsciiStream(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + ClickHouseValue v = getValue(columnIndex); + return v.isNullOrEmpty() ? null : new ByteArrayInputStream(v.asBinary(StandardCharsets.US_ASCII)); } @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getAsciiStream(findColumn(columnLabel)); } @Override @@ -249,14 +251,13 @@ public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLExcepti @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + ClickHouseValue v = getValue(columnIndex); + return v.isNullOrEmpty() ? null : new ByteArrayInputStream(v.asBinary()); } @Override public InputStream getBinaryStream(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getBinaryStream(findColumn(columnLabel)); } @Override @@ -267,8 +268,7 @@ public Blob getBlob(int columnIndex) throws SQLException { @Override public Blob getBlob(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getBlob(findColumn(columnLabel)); } @Override @@ -293,26 +293,23 @@ public byte getByte(String columnLabel) throws SQLException { @Override public byte[] getBytes(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + return getValue(columnIndex).asBinary(); } @Override public byte[] getBytes(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getValue(findColumn(columnLabel)).asBinary(); } @Override public Reader getCharacterStream(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + ClickHouseValue v = getValue(columnIndex); + return v.isNullOrEmpty() ? null : new StringReader(v.asString()); } @Override public Reader getCharacterStream(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getCharacterStream(findColumn(columnLabel)); } @Override @@ -323,8 +320,7 @@ public Clob getClob(int columnIndex) throws SQLException { @Override public Clob getClob(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getClob(findColumn(columnLabel)); } @Override @@ -420,14 +416,12 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public Reader getNCharacterStream(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + return getCharacterStream(columnIndex); } @Override public Reader getNCharacterStream(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getCharacterStream(findColumn(columnLabel)); } @Override @@ -438,8 +432,7 @@ public NClob getNClob(int columnIndex) throws SQLException { @Override public NClob getNClob(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getNClob(findColumn(columnLabel)); } @Override @@ -662,14 +655,13 @@ public URL getURL(String columnLabel) throws SQLException { @Override public InputStream getUnicodeStream(int columnIndex) throws SQLException { - // TODO Auto-generated method stub - return null; + ClickHouseValue v = getValue(columnIndex); + return v.isNullOrEmpty() ? null : new ByteArrayInputStream(v.asBinary(StandardCharsets.UTF_8)); } @Override public InputStream getUnicodeStream(String columnLabel) throws SQLException { - // TODO Auto-generated method stub - return null; + return getUnicodeStream(findColumn(columnLabel)); } @Override diff --git a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/InputBasedPreparedStatement.java b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/InputBasedPreparedStatement.java index 423dd51df..ceb8d77e8 100644 --- a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/InputBasedPreparedStatement.java +++ b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/InputBasedPreparedStatement.java @@ -188,7 +188,7 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException { ensureOpen(); int idx = toArrayIndex(parameterIndex); - values[idx].update(new String(x, StandardCharsets.UTF_8)); + values[idx].update(x); flags[idx] = true; } diff --git a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/SqlBasedPreparedStatement.java b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/SqlBasedPreparedStatement.java index 84066e8e5..0492b5f2a 100644 --- a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/SqlBasedPreparedStatement.java +++ b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/internal/SqlBasedPreparedStatement.java @@ -1,7 +1,6 @@ package com.clickhouse.jdbc.internal; import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Date; import java.sql.ParameterMetaData; @@ -30,6 +29,7 @@ import com.clickhouse.client.ClickHouseValues; import com.clickhouse.client.data.ClickHouseDateTimeValue; import com.clickhouse.client.data.ClickHouseDateValue; +import com.clickhouse.client.data.ClickHouseStringValue; import com.clickhouse.client.logging.Logger; import com.clickhouse.client.logging.LoggerFactory; import com.clickhouse.jdbc.ClickHousePreparedStatement; @@ -337,12 +337,10 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException { int idx = toArrayIndex(parameterIndex); ClickHouseValue value = templates[idx]; - if (value != null) { - value.update(x); - values[idx] = value.toSqlExpression(); - } else { - values[idx] = new String(x, StandardCharsets.UTF_8); + if (value == null) { + templates[idx] = value = ClickHouseStringValue.ofNull(); } + values[idx] = value.update(x).toSqlExpression(); } @Override diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaDataTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaDataTest.java index fe8a16e6a..57d1e0805 100644 --- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaDataTest.java +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseDatabaseMetaDataTest.java @@ -6,10 +6,31 @@ import java.util.Locale; import java.util.Properties; +import com.clickhouse.client.ClickHouseColumn; + import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ClickHouseDatabaseMetaDataTest extends JdbcIntegrationTest { + @DataProvider(name = "selectedColumns") + private Object[][] getSelectedColumns() { + return new Object[][] { + // COLUMN_SIZE, DECIMAL_DIGITS, CHAR_OCTET_LENGTH + // new Object[] { "Bool", 1, null, null }, // Bool was an alias before 21.12 + new Object[] { "Int8", 3, 0, null }, + new Object[] { "UInt8", 3, 0, null }, + new Object[] { "FixedString(3)", 3, null, 3 }, + new Object[] { "String", 0, null, null }, + new Object[] { "Date", 10, 0, null }, + new Object[] { "DateTime64(5)", 29, 5, null }, + new Object[] { "Decimal64(10)", 18, 10, null }, + new Object[] { "Decimal(10,2)", 10, 2, null }, + new Object[] { "Decimal(12,0)", 12, 0, null }, + new Object[] { "Float32", 12, 0, null }, + new Object[] { "Float64", 22, 0, null } }; + } + @Test(groups = "integration") public void testGetTypeInfo() throws SQLException { Properties props = new Properties(); @@ -21,6 +42,26 @@ public void testGetTypeInfo() throws SQLException { } } + @Test(dataProvider = "selectedColumns", groups = "integration") + public void testGetColumns(String columnType, Integer columnSize, Integer decimalDigits, Integer octectLength) + throws SQLException { + ClickHouseColumn c = ClickHouseColumn.of("x", columnType); + String tableName = "test_get_column_" + c.getDataType().name().toLowerCase(); + try (ClickHouseConnection conn = newConnection(new Properties()); + Statement s = conn.createStatement()) { + s.execute("drop table if exists " + tableName + "; " + + "create table " + tableName + "(x " + columnType + ") engine=Memory"); + try (ResultSet rs = conn.getMetaData().getColumns(conn.getCatalog(), conn.getSchema(), tableName, "%")) { + Assert.assertTrue(rs.next(), "Should have one record"); + Assert.assertEquals(rs.getString("cOLUMN_NAME"), "x"); + Assert.assertEquals(rs.getObject("COLUMN_SIZE"), columnSize); + Assert.assertEquals(rs.getObject("DECIMAL_DIGITS"), decimalDigits); + Assert.assertEquals(rs.getObject("CHAR_OCTET_LENGTH"), octectLength); + Assert.assertFalse(rs.next(), "Should have only one record"); + } + } + } + @Test(groups = "integration") public void testTableComment() throws SQLException { String tableName = "test_table_comment"; @@ -35,7 +76,7 @@ public void testTableComment() throws SQLException { s.execute(String.format(Locale.ROOT, "drop table if exists %1$s; create table %1$s(s String) engine=Memory comment '%2$s'", tableName, tableComment)); - try (ResultSet rs = conn.getMetaData().getTables(null, "%", tableName, null)) { + try (ResultSet rs = conn.getMetaData().getTables(conn.getCatalog(), conn.getSchema(), tableName, null)) { Assert.assertTrue(rs.next()); Assert.assertEquals(rs.getString("remarks"), tableComment); Assert.assertFalse(rs.next()); diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHousePreparedStatementTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHousePreparedStatementTest.java index 708bb7327..a8fd937e4 100644 --- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHousePreparedStatementTest.java +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHousePreparedStatementTest.java @@ -34,6 +34,81 @@ private Object[][] getTypedParameters() { LocalDateTime.of(2021, 11, 2, 2, 3, 4) } } }; } + @Test(groups = "integration") + public void testReadWriteBinaryString() throws SQLException { + Properties props = new Properties(); + try (ClickHouseConnection conn = newConnection(props); + Statement s = conn.createStatement()) { + s.execute("drop table if exists test_binary_string; " + + "create table test_binary_string(id Int32, " + + "f0 FixedString(3), f1 Nullable(FixedString(3)), s0 String, s1 Nullable(String))engine=Memory"); + } + + byte[] bytes = new byte[256]; + for (int i = 0; i < 256; i++) { + bytes[i] = (byte) i; + } + try (ClickHouseConnection conn = newConnection(props); + PreparedStatement ps = conn.prepareStatement("select ?, ?")) { + ps.setBytes(1, bytes); + ps.setString(2, Integer.toString(bytes.length)); + ResultSet rs = ps.executeQuery(); + Assert.assertTrue(rs.next()); + Assert.assertEquals(rs.getBytes(1), bytes); + Assert.assertEquals(rs.getInt(2), bytes.length); + Assert.assertFalse(rs.next()); + } + + bytes = new byte[] { 0x61, 0x62, 0x63 }; + try (ClickHouseConnection conn = newConnection(props); + PreparedStatement ps = conn.prepareStatement("insert into test_binary_string")) { + ps.setInt(1, 1); + ps.setBytes(2, bytes); + ps.setBytes(3, null); + ps.setBytes(4, bytes); + ps.setBytes(5, null); + ps.addBatch(); + ps.setInt(1, 2); + ps.setString(2, "abc"); + ps.setString(3, null); + ps.setString(4, "abc"); + ps.setString(5, null); + ps.addBatch(); + ps.setInt(1, 3); + ps.setBytes(2, bytes); + ps.setBytes(3, bytes); + ps.setBytes(4, bytes); + ps.setBytes(5, bytes); + ps.addBatch(); + ps.setInt(1, 4); + ps.setString(2, "abc"); + ps.setString(3, "abc"); + ps.setString(4, "abc"); + ps.setString(5, "abc"); + ps.addBatch(); + ps.executeBatch(); + } + + try (ClickHouseConnection conn = newConnection(props); + PreparedStatement ps = conn + .prepareStatement( + "select distinct * except(id) from test_binary_string where f0 = ? order by id")) { + ps.setBytes(1, bytes); + ResultSet rs = ps.executeQuery(); + Assert.assertTrue(rs.next(), "Should have at least one row"); + Assert.assertEquals(rs.getBytes(1), bytes); + Assert.assertNull(rs.getBytes(2), "f1 should be null"); + Assert.assertEquals(rs.getBytes(3), bytes); + Assert.assertNull(rs.getBytes(4), "s1 should be null"); + Assert.assertTrue(rs.next(), "Should have at least two rows"); + for (int i = 1; i <= 4; i++) { + Assert.assertEquals(rs.getBytes(i), bytes); + Assert.assertEquals(rs.getString(i), "abc"); + } + Assert.assertFalse(rs.next(), "Should not have more than two rows"); + } + } + @Test(groups = "integration") public void testReadWriteDate() throws SQLException { LocalDate d = LocalDate.of(2021, 3, 25); diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseResultSetTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseResultSetTest.java index 412490810..afcb9f5a7 100644 --- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseResultSetTest.java +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseResultSetTest.java @@ -126,6 +126,21 @@ public void testArray() throws SQLException { } } + @Test(groups = "integration") + public void testIpAddress() throws SQLException { + try (ClickHouseConnection conn = newConnection(new Properties()); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt + .executeQuery("select toIPv4('116.253.40.133'), toIPv6('2001:44c8:129:2632:33:0:252:2')"); + Assert.assertTrue(rs.next()); + Assert.assertEquals(rs.getString(1), "116.253.40.133"); + Assert.assertEquals(rs.getObject(1).toString(), "/116.253.40.133"); + Assert.assertEquals(rs.getString(2), "2001:44c8:129:2632:33:0:252:2"); + Assert.assertEquals(rs.getObject(2).toString(), "/2001:44c8:129:2632:33:0:252:2"); + Assert.assertFalse(rs.next()); + } + } + @Test(groups = "integration") public void testTuple() throws SQLException { try (ClickHouseConnection conn = newConnection(new Properties()); diff --git a/examples/grpc/pom.xml b/examples/grpc/pom.xml index 8dfa5f37f..9f012ed79 100644 --- a/examples/grpc/pom.xml +++ b/examples/grpc/pom.xml @@ -56,7 +56,7 @@ UTF-8 UTF-8 - 0.3.2 + 0.3.2-patch3 3.8.1 diff --git a/examples/jdbc/pom.xml b/examples/jdbc/pom.xml index fee05756f..664f21344 100644 --- a/examples/jdbc/pom.xml +++ b/examples/jdbc/pom.xml @@ -56,7 +56,7 @@ UTF-8 UTF-8 - 0.3.2 + 0.3.2-patch3 3.8.1