diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java index 0c596cddf..6db8d47b4 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java @@ -73,17 +73,27 @@ protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings quer protected AtomicBoolean nextRecordEmpty = new AtomicBoolean(true); + /** + * Reads next record into POJO object using set of serializers. + * There should be a serializer for each column in the record, otherwise it will silently skip a field + * It is done in such a way because it is not the reader concern. Calling code should validate this. + * + * Note: internal API + * @param deserializers + * @param obj + * @return + * @throws IOException + */ public boolean readToPOJO(Map deserializers, Object obj ) throws IOException { boolean firstColumn = true; for (ClickHouseColumn column : columns) { try { - Object val = binaryStreamReader.readValue(column); - if (val != null) { - POJOSetter deserializer = deserializers.get(column.getColumnName()); - if (deserializer != null) { - deserializer.setValue(obj, val); - } + POJOSetter deserializer = deserializers.get(column.getColumnName()); + if (deserializer != null) { + deserializer.setValue(obj, binaryStreamReader, column); + } else { + binaryStreamReader.skipValue(column); } firstColumn = false; } catch (EOFException e) { diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java index bb4847bae..74adb519a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java @@ -20,6 +20,7 @@ import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -68,11 +69,28 @@ public class BinaryStreamReader { this.bufferAllocator = bufferAllocator; } + /** + * Reads a value from the internal input stream. + * @param column - column information + * @return value + * @param - target type of the value + * @throws IOException when IO error occurs + */ public T readValue(ClickHouseColumn column) throws IOException { - return readValueImpl(column); + return readValue(column, null); } - private T readValueImpl(ClickHouseColumn column) throws IOException { + /** + * Reads a value from the internal input stream. Method will use type hint to do smarter conversion if possible. + * For example, all datetime values are of {@link ZonedDateTime}; if a type hint is {@link LocalDateTime} then + * {@link ZonedDateTime#toLocalDateTime()} . + * @param column - column information + * @param typeHint - type hint + * @return value + * @param - target type of the value + * @throws IOException when IO error occurs + */ + public T readValue(ClickHouseColumn column, Class typeHint) throws IOException { if (column.isNullable()) { int isNull = readByteOrEOF(input); if (isNull == 1) { // is Null? @@ -95,64 +113,64 @@ private T readValueImpl(ClickHouseColumn column) throws IOException { return (T) new String(readNBytes(input, len), StandardCharsets.UTF_8); } case Int8: - return (T) Byte.valueOf((byte) readByteOrEOF(input)); + return (T) Byte.valueOf(readByte()); case UInt8: - return (T) Short.valueOf(readUnsignedByte(input)); + return (T) Short.valueOf(readUnsignedByte()); case Int16: - return (T) Short.valueOf(readShortLE(input)); + return (T) Short.valueOf(readShortLE()); case UInt16: - return (T) Integer.valueOf(readUnsignedShortLE(input)); + return (T) Integer.valueOf(readUnsignedShortLE()); case Int32: - return (T) Integer.valueOf(readIntLE(input)); + return (T) Integer.valueOf(readIntLE()); case UInt32: - return (T) Long.valueOf(readUnsignedIntLE(input)); + return (T) Long.valueOf(readUnsignedIntLE()); case Int64: - return (T) Long.valueOf(readLongLE(input)); + return (T) Long.valueOf(readLongLE()); case UInt64: - return (T) readBigIntegerLE(input, INT64_SIZE, true); + return (T) readBigIntegerLE(INT64_SIZE, true); case Int128: - return (T) readBigIntegerLE(input, INT128_SIZE, false); + return (T) readBigIntegerLE(INT128_SIZE, false); case UInt128: - return (T) readBigIntegerLE(input, INT128_SIZE, true); + return (T) readBigIntegerLE(INT128_SIZE, true); case Int256: - return (T) readBigIntegerLE(input, INT256_SIZE, false); + return (T) readBigIntegerLE(INT256_SIZE, false); case UInt256: - return (T) readBigIntegerLE(input, INT256_SIZE, true); + return (T) readBigIntegerLE(INT256_SIZE, true); case Decimal: - return (T) readDecimal(input, column.getPrecision(), column.getScale()); + return (T) readDecimal(column.getPrecision(), column.getScale()); case Decimal32: - return (T) readDecimal(input, ClickHouseDataType.Decimal32.getMaxPrecision(), column.getScale()); + return (T) readDecimal(ClickHouseDataType.Decimal32.getMaxPrecision(), column.getScale()); case Decimal64: - return (T) readDecimal(input, ClickHouseDataType.Decimal64.getMaxPrecision(), column.getScale()); + return (T) readDecimal(ClickHouseDataType.Decimal64.getMaxPrecision(), column.getScale()); case Decimal128: - return (T) readDecimal(input, ClickHouseDataType.Decimal128.getMaxPrecision(), column.getScale()); + return (T) readDecimal(ClickHouseDataType.Decimal128.getMaxPrecision(), column.getScale()); case Decimal256: - return (T) readDecimal(input, ClickHouseDataType.Decimal256.getMaxPrecision(), column.getScale()); + return (T) readDecimal(ClickHouseDataType.Decimal256.getMaxPrecision(), column.getScale()); case Float32: - return (T) Float.valueOf(readFloatLE(input)); + return (T) Float.valueOf(readFloatLE()); case Float64: - return (T) Double.valueOf(readDoubleLE(input)); + return (T) Double.valueOf(readDoubleLE()); case Bool: return (T) Boolean.valueOf(readByteOrEOF(input) == 1); case Enum8: - return (T) Byte.valueOf((byte) readUnsignedByte(input)); + return (T) Byte.valueOf((byte) readUnsignedByte()); case Enum16: - return (T) Short.valueOf((short) readUnsignedShortLE(input)); + return (T) Short.valueOf((short) readUnsignedShortLE()); case Date: - return (T) readDate(input, column.getTimeZone() == null ? timeZone : - column.getTimeZone()); + return convertDateTime(readDate(column.getTimeZone() == null ? timeZone : + column.getTimeZone()), typeHint); case Date32: - return (T) readDate32(input, column.getTimeZone() == null ? timeZone : - column.getTimeZone()); + return convertDateTime(readDate32(column.getTimeZone() == null ? timeZone : + column.getTimeZone()), typeHint); case DateTime: - return (T) readDateTime32(input, column.getTimeZone() == null ? timeZone : - column.getTimeZone()); + return convertDateTime(readDateTime32(column.getTimeZone() == null ? timeZone : + column.getTimeZone()), typeHint); case DateTime32: - return (T) readDateTime32(input, column.getTimeZone() == null ? timeZone : - column.getTimeZone()); + return convertDateTime(readDateTime32(column.getTimeZone() == null ? timeZone : + column.getTimeZone()), typeHint); case DateTime64: - return (T) readDateTime64(input, 3, column.getTimeZone() == null ? timeZone : - column.getTimeZone()); + return convertDateTime(readDateTime64(3, column.getTimeZone() == null ? timeZone : + column.getTimeZone()), typeHint); case IntervalYear: case IntervalQuarter: @@ -165,7 +183,7 @@ private T readValueImpl(ClickHouseColumn column) throws IOException { case IntervalMicrosecond: case IntervalMillisecond: case IntervalNanosecond: - return (T) readBigIntegerLE(input, 8, true); + return (T) readBigIntegerLE(8, true); case IPv4: // https://clickhouse.com/docs/en/sql-reference/data-types/ipv4 return (T) Inet4Address.getByAddress(readNBytesLE(input, 4)); @@ -173,20 +191,20 @@ private T readValueImpl(ClickHouseColumn column) throws IOException { // https://clickhouse.com/docs/en/sql-reference/data-types/ipv6 return (T) Inet6Address.getByAddress(readNBytes(input, 16)); case UUID: - return (T) new UUID(readLongLE(input), readLongLE(input)); + return (T) new UUID(readLongLE(), readLongLE()); case Point: - return (T) readGeoPoint(input); + return (T) readGeoPoint(); case Polygon: - return (T) readGeoPolygon(input); + return (T) readGeoPolygon(); case MultiPolygon: - return (T) readGeoMultiPolygon(input); + return (T) readGeoMultiPolygon(); case Ring: - return (T) readGeoRing(input); + return (T) readGeoRing(); // case JSON: // obsolete https://clickhouse.com/docs/en/sql-reference/data-types/json#displaying-json-column // case Object: case Array: - return (T) readArray(column); + return convertArray(readArray(column), typeHint); case Map: return (T) readMap(column); // case Nested: @@ -206,7 +224,37 @@ private T readValueImpl(ClickHouseColumn column) throws IOException { } } - private short readShortLE(InputStream input) throws IOException { + private static T convertDateTime(ZonedDateTime value, Class typeHint) { + if (typeHint == null) { + return (T) value; + } + if (typeHint.isAssignableFrom(LocalDateTime.class)) { + return (T) value.toLocalDateTime(); + } else if (typeHint.isAssignableFrom(LocalDate.class)) { + return (T) value.toLocalDate(); + } + + return (T) value; + } + + private static T convertArray(ArrayValue value, Class typeHint) { + if (typeHint == null) { + return (T) value; + } + if (typeHint.isAssignableFrom(List.class)) { + return (T) value.asList(); + } + + return (T) value; + } + + /** + * Read a short value in little-endian from the internal input stream. + * + * @return short value + * @throws IOException when IO error occurs + */ + public short readShortLE() throws IOException { return readShortLE(input, bufferAllocator.allocate(INT16_SIZE)); } @@ -216,14 +264,19 @@ private short readShortLE(InputStream input) throws IOException { * @param input - source of bytes * @param buff - buffer to store data * @return short value - * @throws IOException + * @throws IOException when IO error occurs */ public static short readShortLE(InputStream input, byte[] buff) throws IOException { readNBytes(input, buff, 0, 2); return (short) (buff[0] & 0xFF | (buff[1] & 0xFF) << 8); } - private int readIntLE(InputStream input) throws IOException { + /** + * Reads an int value in little-endian from the internal input stream. + * @return int value + * @throws IOException when IO error occurs + */ + public int readIntLE() throws IOException { return readIntLE(input, bufferAllocator.allocate(INT32_SIZE)); } @@ -233,14 +286,20 @@ private int readIntLE(InputStream input) throws IOException { * @param input - source of bytes * @param buff - buffer to store data * @return - int value - * @throws IOException + * @throws IOException when IO error occurs */ public static int readIntLE(InputStream input, byte[] buff) throws IOException { readNBytes(input, buff, 0, 4); return (buff[0] & 0xFF) | (buff[1] & 0xFF) << 8 | (buff[2] & 0xFF) << 16 | (buff[3] & 0xFF) << 24; } - private long readLongLE(InputStream input) throws IOException { + /** + * Reads a long value in little-endian from the internal input stream. + * + * @return long value + * @throws IOException when IO error occurs + */ + public long readLongLE() throws IOException { return readLongLE(input, bufferAllocator.allocate(INT64_SIZE)); } @@ -250,7 +309,7 @@ private long readLongLE(InputStream input) throws IOException { * @param input - source of bytes * @param buff - buffer to store data * @return - long value - * @throws IOException + * @throws IOException when IO error occurs */ public static long readLongLE(InputStream input, byte[] buff) throws IOException { readNBytes(input, buff, 0, 8); @@ -259,11 +318,30 @@ public static long readLongLE(InputStream input, byte[] buff) throws IOException | (long) (buff[6] & 0xFF) << 48 | (long) (buff[7] & 0xFF) << 56; } - public short readUnsignedByte(InputStream input) throws IOException { + /** + * Read byte from the internal input stream. + * @return byte value + * @throws IOException when IO error occurs + */ + public byte readByte() throws IOException { + return (byte) readByteOrEOF(input); + } + + /** + * Reads an unsigned byte value from the internal input stream. + * @return unsigned byte value + * @throws IOException when IO error occurs + */ + public short readUnsignedByte() throws IOException { return (short) (readByteOrEOF(input) & 0xFF); } - private int readUnsignedShortLE(InputStream input) throws IOException { + /** + * Reads an unsigned short value from the internal input stream. + * @return unsigned short value + * @throws IOException when IO error occurs + */ + public int readUnsignedShortLE() throws IOException { return readUnsignedShortLE(input, bufferAllocator.allocate(INT16_SIZE)); } @@ -279,8 +357,14 @@ public static int readUnsignedShortLE(InputStream input, byte[] buff) throws IOE return readShortLE(input, buff) & 0xFFFF; } - private long readUnsignedIntLE(InputStream input) throws IOException { - return readIntLE(input) & 0xFFFFFFFFL; + /** + * Reads an unsigned int value in little-endian from the internal input stream. + * + * @return unsigned int value + * @throws IOException when IO error occurs + */ + public long readUnsignedIntLE() throws IOException { + return readIntLE() & 0xFFFFFFFFL; } /** @@ -288,14 +372,21 @@ private long readUnsignedIntLE(InputStream input) throws IOException { * * @param input - source of bytes * @param buff - buffer to store data - * @return - * @throws IOException + * @return - unsigned int value + * @throws IOException when IO error occurs */ public static long readUnsignedIntLE(InputStream input, byte[] buff) throws IOException { return readIntLE(input, buff) & 0xFFFFFFFFL; } - private BigInteger readBigIntegerLE(InputStream input, int len, boolean unsigned) throws IOException { + /** + * Reads a big integer value in little-endian from the internal input stream. + * @param len - number of bytes to read + * @param unsigned - whether the value is unsigned + * @return big integer value + * @throws IOException when IO error occurs + */ + public BigInteger readBigIntegerLE(int len, boolean unsigned) throws IOException { return readBigIntegerLE(input, bufferAllocator.allocate(len), len, unsigned); } @@ -323,25 +414,43 @@ public static BigInteger readBigIntegerLE(InputStream input, byte[] buff, int le return unsigned ? new BigInteger(1, bytes) : new BigInteger(bytes); } - public float readFloatLE(InputStream input) throws IOException { - return Float.intBitsToFloat(readIntLE(input)); + + /** + * Reads a decimal value from the internal input stream. + * @return decimal value + * @throws IOException when IO error occurs + */ + public float readFloatLE() throws IOException { + return Float.intBitsToFloat(readIntLE()); } - public double readDoubleLE(InputStream input) throws IOException { - return Double.longBitsToDouble(readLongLE(input)); + /** + * Reads a double value from the internal input stream. + * @return double value + * @throws IOException when IO error occurs + */ + public double readDoubleLE() throws IOException { + return Double.longBitsToDouble(readLongLE()); } - private BigDecimal readDecimal(InputStream input, int precision, int scale) throws IOException { + /** + * Reads a decimal value from the internal input stream. + * @param precision - precision of the decimal value + * @param scale - scale of the decimal value + * @return decimal value + * @throws IOException when IO error occurs + */ + public BigDecimal readDecimal(int precision, int scale) throws IOException { BigDecimal v; if (precision <= ClickHouseDataType.Decimal32.getMaxScale()) { - return BigDecimal.valueOf(readIntLE(input), scale); + return BigDecimal.valueOf(readIntLE(), scale); } else if (precision <= ClickHouseDataType.Decimal64.getMaxScale()) { - v = BigDecimal.valueOf(readLongLE(input), scale); + v = BigDecimal.valueOf(readLongLE(), scale); } else if (precision <= ClickHouseDataType.Decimal128.getMaxScale()) { - v = new BigDecimal(readBigIntegerLE(input, INT128_SIZE, false), scale); + v = new BigDecimal(readBigIntegerLE(INT128_SIZE, false), scale); } else { - v = new BigDecimal(readBigIntegerLE(input, INT256_SIZE, false), scale); + v = new BigDecimal(readBigIntegerLE(INT256_SIZE, false), scale); } return v; @@ -403,8 +512,13 @@ public static byte[] readNBytesLE(InputStream input, byte[] buffer, int offset, return bytes; } - - private ArrayValue readArray(ClickHouseColumn column) throws IOException { + /** + * Reads a array into an ArrayValue object. + * @param column - column information + * @return array value + * @throws IOException when IO error occurs + */ + public ArrayValue readArray(ClickHouseColumn column) throws IOException { Class itemType = column.getArrayBaseColumn().getDataType().getWiderPrimitiveClass(); int len = readVarInt(input); ArrayValue array = new ArrayValue(column.getArrayNestedLevel() > 1 ? ArrayValue.class : itemType, len); @@ -414,12 +528,16 @@ private ArrayValue readArray(ClickHouseColumn column) throws IOException { } for (int i = 0; i < len; i++) { - array.set(i, readValueImpl(column.getNestedColumns().get(0))); + array.set(i, readValue(column.getNestedColumns().get(0))); } return array; } + public void skipValue(ClickHouseColumn column) throws IOException { + readValue(column, null); + } + public static class ArrayValue { final int length; @@ -479,7 +597,13 @@ public synchronized List asList() { } } - private Map readMap(ClickHouseColumn column) throws IOException { + /** + * Reads a map. + * @param column - column information + * @return a map + * @throws IOException when IO error occurs + */ + public Map readMap(ClickHouseColumn column) throws IOException { int len = readVarInt(input); if (len == 0) { return Collections.emptyMap(); @@ -489,51 +613,77 @@ public synchronized List asList() { ClickHouseColumn valueType = column.getValueInfo(); LinkedHashMap map = new LinkedHashMap<>(len); for (int i = 0; i < len; i++) { - Object key = readValueImpl(keyType); - Object value = readValueImpl(valueType); + Object key = readValue(keyType); + Object value = readValue(valueType); map.put(key, value); } return map; } - private Object[] readTuple(ClickHouseColumn column) throws IOException { + /** + * Reads a tuple. + * @param column - column information + * @return a tuple + * @throws IOException when IO error occurs + */ + public Object[] readTuple(ClickHouseColumn column) throws IOException { int len = column.getNestedColumns().size(); Object[] tuple = new Object[len]; for (int i = 0; i < len; i++) { - tuple[i] = readValueImpl(column.getNestedColumns().get(i)); + tuple[i] = readValue(column.getNestedColumns().get(i)); } return tuple; } - private double[] readGeoPoint(InputStream input) throws IOException { - return new double[]{readDoubleLE(input), readDoubleLE(input)}; + /** + * Reads a GEO point as an array of two doubles what represents coordinates (X, Y). + * @return X, Y coordinates + * @throws IOException when IO error occurs + */ + public double[] readGeoPoint() throws IOException { + return new double[]{readDoubleLE(), readDoubleLE()}; } - private double[][] readGeoRing(InputStream input) throws IOException { + /** + * Reads a GEO ring as an array of points. + * @return array of points + * @throws IOException when IO error occurs + */ + public double[][] readGeoRing() throws IOException { int count = readVarInt(input); double[][] value = new double[count][2]; for (int i = 0; i < count; i++) { - value[i] = readGeoPoint(input); + value[i] = readGeoPoint(); } return value; } - private double[][][] readGeoPolygon(InputStream input) throws IOException { + /** + * Reads a GEO polygon as an array of rings. + * @return polygon + * @throws IOException when IO error occurs + */ + public double[][][] readGeoPolygon() throws IOException { int count = readVarInt(input); double[][][] value = new double[count][][]; for (int i = 0; i < count; i++) { - value[i] = readGeoRing(input); + value[i] = readGeoRing(); } return value; } - private double[][][][] readGeoMultiPolygon(InputStream input) throws IOException { + /** + * Reads a GEO multipolygon as an array of polygons. + * @return multipolygon + * @throws IOException when IO error occurs + */ + private double[][][][] readGeoMultiPolygon() throws IOException { int count = readVarInt(input); double[][][][] value = new double[count][][][]; for (int i = 0; i < count; i++) { - value[i] = readGeoPolygon(input); + value[i] = readGeoPolygon(); } return value; } @@ -560,7 +710,13 @@ public static int readVarInt(InputStream input) throws IOException { return value; } - private ZonedDateTime readDate(InputStream input, TimeZone tz) throws IOException { + /** + * Reads a Date value from internal input stream. + * @param tz - timezone + * @return ZonedDateTime + * @throws IOException when IO error occurs + */ + private ZonedDateTime readDate(TimeZone tz) throws IOException { return readDate(input, bufferAllocator.allocate(INT16_SIZE), tz); } @@ -569,15 +725,21 @@ private ZonedDateTime readDate(InputStream input, TimeZone tz) throws IOExceptio * @param input - source of bytes * @param buff - for reading short value. Should be 2 bytes. * @param tz - timezone - * @return - * @throws IOException + * @return ZonedDateTime + * @throws IOException when IO error occurs */ public static ZonedDateTime readDate(InputStream input, byte[] buff, TimeZone tz) throws IOException { LocalDate d = LocalDate.ofEpochDay(readUnsignedShortLE(input, buff)); return d.atStartOfDay(tz.toZoneId()).withZoneSameInstant(tz.toZoneId()); } - private ZonedDateTime readDate32(InputStream input, TimeZone tz) + /** + * Reads a Date32 value from internal input stream. + * @param tz - timezone + * @return ZonedDateTime + * @throws IOException when IO error occurs + */ + public ZonedDateTime readDate32(TimeZone tz) throws IOException { return readDate32(input, bufferAllocator.allocate(INT32_SIZE), tz); } @@ -588,8 +750,8 @@ private ZonedDateTime readDate32(InputStream input, TimeZone tz) * @param input - source of bytes * @param buff - for reading int value. Should be 4 bytes. * @param tz - timezone - * @return - * @throws IOException + * @return ZonedDateTime + * @throws IOException when IO error occurs */ public static ZonedDateTime readDate32(InputStream input, byte[] buff, TimeZone tz) throws IOException { @@ -597,7 +759,7 @@ public static ZonedDateTime readDate32(InputStream input, byte[] buff, TimeZone return d.atStartOfDay(tz.toZoneId()).withZoneSameInstant(tz.toZoneId()); } - private ZonedDateTime readDateTime32(InputStream input, TimeZone tz) throws IOException { + private ZonedDateTime readDateTime32(TimeZone tz) throws IOException { return readDateTime32(input, bufferAllocator.allocate(INT32_SIZE), tz); } @@ -607,17 +769,28 @@ private ZonedDateTime readDateTime32(InputStream input, TimeZone tz) throws IOEx * @param buff - for reading int value. Should be 4 bytes. * @param tz - timezone * @return ZonedDateTime - * @throws IOException + * @throws IOException when IO error occurs */ public static ZonedDateTime readDateTime32(InputStream input, byte[] buff, TimeZone tz) throws IOException { long time = readUnsignedIntLE(input, buff); return LocalDateTime.ofInstant(Instant.ofEpochSecond(Math.max(time, 0L)), tz.toZoneId()).atZone(tz.toZoneId()); } - private ZonedDateTime readDateTime64(InputStream input, int scale, TimeZone tz) throws IOException { + /** + * Reads a datetime64 from internal input stream. + * @param scale - scale of the datetime64 + * @param tz - timezone + * @return ZonedDateTime + * @throws IOException when IO error occurs + */ + public ZonedDateTime readDateTime64(int scale, TimeZone tz) throws IOException { return readDateTime64(input, bufferAllocator.allocate(INT64_SIZE), scale, tz); } + + /** + * Bases for datetime64. + */ public static final int[] BASES = new int[]{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; @@ -651,6 +824,12 @@ public static ZonedDateTime readDateTime64(InputStream input, byte[] buff, int s .atZone(tz.toZoneId()); } + /** + * Reads a decimal value from input stream. + * @param input - source of bytes + * @return String + * @throws IOException when IO error occurs + */ public static String readString(InputStream input) throws IOException { int len = readVarInt(input); if (len == 0) { @@ -659,7 +838,7 @@ public static String readString(InputStream input) throws IOException { return new String(readNBytes(input, len), StandardCharsets.UTF_8); } - private static int readByteOrEOF(InputStream input) throws IOException { + public static int readByteOrEOF(InputStream input) throws IOException { int b = input.read(); if (b < 0) { throw new EOFException("End of stream reached before reading all data"); @@ -681,6 +860,25 @@ public byte[] allocate(int size) { } } + public static boolean isReadToPrimitive(ClickHouseDataType dataType) { + switch (dataType) { + case Int8: + case UInt8: + case Int16: + case UInt16: + case Int32: + case UInt32: + case Int64: + case Float32: + case Float64: + case Bool: + case Enum8: + case Enum16: + return true; + default: + return false; + } + } /** * Byte allocator that caches preallocated byte arrays for small sizes. */ diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/SerializerUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/SerializerUtils.java index 148ff8c1d..96a78643a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/internal/SerializerUtils.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/SerializerUtils.java @@ -9,7 +9,6 @@ import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.format.BinaryStreamUtils; import com.clickhouse.data.value.ClickHouseBitmap; - import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -26,7 +25,6 @@ import java.net.Inet6Address; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZonedDateTime; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -35,16 +33,12 @@ import java.util.Set; import java.util.StringTokenizer; import java.util.UUID; -import java.util.stream.Collector; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.CHECKCAST; -import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.RETURN; @@ -304,145 +298,55 @@ public static POJOSetter compilePOJOSetter(Method setterMethod, ClickHouseColumn mv.visitEnd(); } - /* Currently all readers operate with objects and next scenarios are possible: - - target is primitive and source is a boxed type - - source should be called `intValue()` or similar - - target and source are both objects - - no casting is needed - - target is a boxed type and source is too, but smaller - - source should be called `intValue()` or similar (target should be used to detect primitive type) - - then target should be boxed with `valueOf()` - - target is the assignable from source (e.g. target is `Object` and source is `String`) - - no casting is needed - - source should be converted before assigning to the target - - call conversion function - - In the future when reader will use primitive types then call to `valueOf()` should be - added for boxed types. - */ - - Class targetType = setterMethod.getParameterTypes()[0]; - Class targetPrimitiveType = ClickHouseDataType.toPrimitiveType(targetType); // will return object class if no primitive - Class sourceType = column.getDataType().getObjectClass(); // will return object class if no primitive - + /* + * Next code will generate instance of POJOSetter that will + * call BinaryStreamReader.read* method to read value from stream + * + */ // setter setValue(Object obj, Object value) impl { MethodVisitor mv = writer.visitMethod(ACC_PUBLIC, "setValue", - pojoSetterMethodDescriptor(Object.class, Object.class), null, null); + Type.getMethodDescriptor(Type.VOID_TYPE, + Type.getType(Object.class), Type.getType(BinaryStreamReader.class), + Type.getType(ClickHouseColumn.class)), null, new String[]{"java/io/IOException"}); + + Class targetType = setterMethod.getParameterTypes()[0]; + Class targetPrimitiveType = ClickHouseDataType.toPrimitiveType(targetType); // will return object class if no primitive + mv.visitCode(); - mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 1); // load target object mv.visitTypeInsn(CHECKCAST, Type.getInternalName(dtoClass)); + mv.visitVarInsn(ALOAD, 2); // load reader - if (sourceType == LocalDate.class) { - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(ZonedDateTime.class)); - mv.visitMethodInsn(INVOKEVIRTUAL, - Type.getInternalName(ZonedDateTime.class), - "toLocalDate", - "()" + Type.getDescriptor(LocalDate.class), - false); - } else if (sourceType == LocalDateTime.class) { - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(ZonedDateTime.class)); - mv.visitMethodInsn(INVOKEVIRTUAL, - Type.getInternalName(ZonedDateTime.class), - "toLocalDateTime", - "()" + Type.getDescriptor(LocalDateTime.class), - false); - } else if ((targetType == boolean.class || targetType == Boolean.class) && column.getDataType() != ClickHouseDataType.Bool) { - mv.visitVarInsn(ALOAD, 2); // load object - String sourceInternalClassName; - if (column.getDataType().isSigned()) { - sourceInternalClassName = Type.getInternalName(sourceType); - } else if (column.getDataType() == ClickHouseDataType.UInt64) { - sourceInternalClassName = Type.getInternalName(BigInteger.class); - } else { - sourceInternalClassName = Type.getInternalName( - ClickHouseDataType.toObjectType(ClickHouseDataType.toWiderPrimitiveType( - ClickHouseDataType.toPrimitiveType(sourceType)))); - } - mv.visitTypeInsn(CHECKCAST, sourceInternalClassName); - mv.visitMethodInsn(INVOKESTATIC, - Type.getInternalName(SerializerUtils.class), - "convertToBoolean", - "(" + Type.getDescriptor(Object.class) + ")" + Type.getDescriptor(boolean.class), - false); - if (!targetType.isPrimitive()) { - mv.visitMethodInsn(INVOKEVIRTUAL, - Type.getInternalName(Boolean.class), - "valueOf", - "(" + Type.getDescriptor(boolean.class) + ")" + Type.getDescriptor(targetType), - false); - } - } else if (column.getDataType() == ClickHouseDataType.Tuple && targetType.isAssignableFrom(List.class)) { - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Object[].class)); - mv.visitMethodInsn(INVOKESTATIC, - Type.getInternalName(Arrays.class), - "stream", - "([Ljava/lang/Object;)" + Type.getDescriptor(Stream.class), - false); - mv.visitMethodInsn(INVOKESTATIC, - Type.getInternalName(Collectors.class), - "toList", - "()" + Type.getDescriptor(Collector.class), - false); - mv.visitMethodInsn(INVOKEINTERFACE, - Type.getInternalName(Stream.class), - "collect", - "(" + Type.getDescriptor(Collector.class) + ")" + Type.getDescriptor(Object.class), - true); - } else if (targetType.isAssignableFrom(sourceType)) { // assuming source is always object because of reader - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(sourceType)); - } else if (column.getDataType() == ClickHouseDataType.Array) { - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(BinaryStreamReader.ArrayValue.class)); + if (targetType.isPrimitive() && BinaryStreamReader.isReadToPrimitive(column.getDataType())) { + binaryReaderMethodForType(mv, + targetPrimitiveType, column.getDataType()); + } else if (targetType.isPrimitive() && column.getDataType() == ClickHouseDataType.UInt64) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(BigInteger.class)); mv.visitMethodInsn(INVOKEVIRTUAL, - Type.getInternalName(BinaryStreamReader.ArrayValue.class), - "asList", - "()" + Type.getDescriptor(List.class), - false); - } else if (targetType.isPrimitive() && !targetType.isArray()) { - // unboxing - mv.visitVarInsn(ALOAD, 2); // load object - String sourceInternalClassName = getSourceInternalClassName(column, sourceType); - mv.visitTypeInsn(CHECKCAST, sourceInternalClassName); - mv.visitMethodInsn(INVOKEVIRTUAL, - sourceInternalClassName, + Type.getInternalName(BigInteger.class), targetType.getSimpleName() + "Value", "()" + Type.getDescriptor(targetType), false); - } else if (!targetPrimitiveType.isPrimitive()) { - // boxing - String sourceInternalClassName = getSourceInternalClassName(column, sourceType); - mv.visitVarInsn(ALOAD, 2); // load object - mv.visitTypeInsn(CHECKCAST, sourceInternalClassName); - try { - if (!targetType.isAssignableFrom(Class.forName(sourceInternalClassName - .replaceAll("/", ".")))) { - mv.visitMethodInsn(INVOKEVIRTUAL, - sourceInternalClassName, - targetPrimitiveType.getSimpleName() + "Value", - "()" + Type.getDescriptor(targetPrimitiveType), - false); - mv.visitMethodInsn(INVOKESTATIC, - Type.getInternalName(targetType), - "valueOf", - "(" + Type.getDescriptor(targetPrimitiveType) + ")" + Type.getDescriptor(targetType), - false); - } - } catch (ClassNotFoundException e) { - throw new ClientException("Cannot find class " + sourceInternalClassName + " to compile deserializer for " - + column.getColumnName(), e); - } } else { - throw new ClientException("Unsupported conversion from " + sourceType + " to " + targetType); + mv.visitVarInsn(ALOAD, 3); // column + // load target class into stack + mv.visitLdcInsn(Type.getType(targetType)); + // call readValue method + mv.visitMethodInsn(INVOKEVIRTUAL, + Type.getInternalName(BinaryStreamReader.class), + "readValue", + Type.getMethodDescriptor( + Type.getType(Object.class), + Type.getType(ClickHouseColumn.class), + Type.getType(Class.class)), + false); + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(targetType)); + // cast to target type } - - // finally call setter with the result of last INVOKEVIRTUAL + // finally call setter with the result of target class mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(dtoClass), setterMethod.getName(), @@ -463,32 +367,124 @@ public static POJOSetter compilePOJOSetter(Method setterMethod, ClickHouseColumn } } - private static String getSourceInternalClassName(ClickHouseColumn column, Class sourceType) { - String sourceInternalClassName; - if (column.getDataType().isSigned()) { - sourceInternalClassName = Type.getInternalName(sourceType); - } else if (column.getDataType() == ClickHouseDataType.UInt64) { - sourceInternalClassName = Type.getInternalName(BigInteger.class); - } else if (column.getDataType() == ClickHouseDataType.Enum8) { - sourceInternalClassName = Type.getInternalName(Byte.class); - } else if (column.getDataType() == ClickHouseDataType.Enum16) { - sourceInternalClassName = Type.getInternalName(Short.class); - } else { - sourceInternalClassName = Type.getInternalName( - ClickHouseDataType.toObjectType(ClickHouseDataType.toWiderPrimitiveType( - ClickHouseDataType.toPrimitiveType(sourceType)))); + private static void binaryReaderMethodForType(MethodVisitor mv, Class targetType, ClickHouseDataType dataType) { + String readerMethod = null; + String readerMethodReturnType = null; + int convertOpcode = -1; + + switch (dataType) { + case Int8: + readerMethod = "readByte"; + readerMethodReturnType = Type.getDescriptor(byte.class); + break; + case UInt8: + readerMethod = "readUnsignedByte"; + readerMethodReturnType = Type.getDescriptor(short.class); + break; + case Int16: + readerMethod = "readShortLE"; + readerMethodReturnType = Type.getDescriptor(short.class); + break; + case UInt16: + readerMethod = "readUnsignedShortLE"; + readerMethodReturnType = Type.getDescriptor(int.class); + convertOpcode = intToOpcode(targetType); + break; + case Int32: + readerMethod = "readIntLE"; + readerMethodReturnType = Type.getDescriptor(int.class); + convertOpcode = intToOpcode(targetType); + break; + case UInt32: + readerMethod = "readUnsignedIntLE"; + readerMethodReturnType = Type.getDescriptor(long.class); + convertOpcode = longToOpcode(targetType); + break; + case Int64: + readerMethod = "readLongLE"; + readerMethodReturnType = Type.getDescriptor(long.class); + convertOpcode = longToOpcode(targetType); + break; + case Float32: + readerMethod = "readFloatLE"; + readerMethodReturnType = Type.getDescriptor(float.class); + convertOpcode = floatToOpcode(targetType); + break; + case Float64: + readerMethod = "readDoubleLE"; + readerMethodReturnType = Type.getDescriptor(double.class); + convertOpcode = doubleToOpcode(targetType); + break; + case Enum8: + readerMethod = "readByte"; + readerMethodReturnType = Type.getDescriptor(byte.class); + break; + case Enum16: + readerMethod = "readShortLE"; + readerMethodReturnType = Type.getDescriptor(short.class); + break; + default: + throw new ClientException("Column type '" + dataType + "' cannot be set to a primitive type '" + targetType + "'"); + } + + mv.visitMethodInsn(INVOKEVIRTUAL, + Type.getInternalName(BinaryStreamReader.class), + readerMethod, + "()" +readerMethodReturnType, + false); + if (convertOpcode != -1) { + mv.visitInsn(convertOpcode); } - return sourceInternalClassName; } - private static String pojoSetterMethodDescriptor(Class dtoClass, Class argType) { - StringBuilder sb = new StringBuilder(); - sb.append('('); - sb.append(Type.getDescriptor(dtoClass)); - sb.append(Type.getDescriptor(argType)); - sb.append(')'); - sb.append('V'); - return sb.toString(); + private static int intToOpcode(Class targetType) { + if (targetType == short.class) { + return Opcodes.I2S; + } else if (targetType == long.class) { + return Opcodes.I2L; + } else if (targetType == byte.class) { + return Opcodes.I2B; + } else if (targetType == char.class) { + return Opcodes.I2C; + } else if (targetType == float.class) { + return Opcodes.I2F; + } else if (targetType == double.class) { + return Opcodes.I2D; + } + return -1; + } + + private static int longToOpcode(Class targetType) { + if (targetType == int.class) { + return Opcodes.L2I; + } else if (targetType == float.class) { + return Opcodes.L2F; + } else if (targetType == double.class) { + return Opcodes.L2D; + } + return -1; + } + + private static int floatToOpcode(Class targetType) { + if (targetType == int.class) { + return Opcodes.F2I; + } else if (targetType == long.class) { + return Opcodes.F2L; + } else if (targetType == double.class) { + return Opcodes.F2D; + } + return -1; + } + + private static int doubleToOpcode(Class targetType) { + if (targetType == int.class) { + return Opcodes.D2I; + } else if (targetType == long.class) { + return Opcodes.D2L; + } else if (targetType == float.class) { + return Opcodes.D2F; + } + return -1; } public static class DynamicClassLoader extends ClassLoader { diff --git a/client-v2/src/main/java/com/clickhouse/client/api/query/POJOSetter.java b/client-v2/src/main/java/com/clickhouse/client/api/query/POJOSetter.java index dac98156c..c673e16c4 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/query/POJOSetter.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/query/POJOSetter.java @@ -1,6 +1,9 @@ package com.clickhouse.client.api.query; +import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; +import com.clickhouse.data.ClickHouseColumn; + /** * Class used to set value for individual fields in a POJO. * Implementation will have reference to a specific POJO property. @@ -9,37 +12,5 @@ */ public interface POJOSetter { - default void setValue(Object obj, boolean value) { - throw new UnsupportedOperationException("Unsupported type: boolean"); - }; - - default void setValue(Object obj, byte value) { - throw new UnsupportedOperationException("Unsupported type: byte"); - }; - - default void setValue(Object obj, char value) { - throw new UnsupportedOperationException("Unsupported type: char"); - }; - - default void setValue(Object obj, short value) { - throw new UnsupportedOperationException("Unsupported type: short"); - }; - - default void setValue(Object obj, int value) { - throw new UnsupportedOperationException("Unsupported type: int"); - }; - - default void setValue(Object obj, long value) { - throw new UnsupportedOperationException("Unsupported type: long"); - }; - - default void setValue(Object obj, float value) { - throw new UnsupportedOperationException("Unsupported type: float"); - }; - - default void setValue(Object obj, double value) { - throw new UnsupportedOperationException("Unsupported type: double"); - }; - - void setValue(Object obj, Object value); + void setValue(Object obj, BinaryStreamReader reader, ClickHouseColumn column) throws Exception; } diff --git a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java index ba168f522..a569600bf 100644 --- a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java @@ -1,62 +1,28 @@ package com.clickhouse.client.internal; -import com.clickhouse.client.api.internal.SerializerUtils; +import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; import com.clickhouse.client.api.query.POJOSetter; -import com.clickhouse.client.query.SamplePOJO; +import com.clickhouse.client.query.QuerySamplePOJO; +import com.clickhouse.client.query.SimplePOJO; import com.clickhouse.data.ClickHouseColumn; -import org.testng.Assert; -import org.testng.annotations.Test; -import java.lang.reflect.Method; -import java.math.BigInteger; +import java.io.IOException; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; +import java.time.ZonedDateTime; public class SerializerUtilsTests { - @Test(enabled = false) - public void testDeserialize() throws Exception { - - Map pojoSetterList = new HashMap<>(); - for (Method method : SamplePOJOForSerialization.class.getDeclaredMethods()) { - if (method.getName().startsWith("set")) { - pojoSetterList.put(method.getName().substring(3).toLowerCase(), - SerializerUtils.compilePOJOSetter(method, ClickHouseColumn.of(method.getName(), - "String"))); - } - } - - SamplePOJOForSerialization pojo = new SamplePOJOForSerialization(); - pojoSetterList.get("string").setValue(pojo, "John Doe"); - pojoSetterList.get("int32").setValue(pojo, Integer.valueOf(30)); - pojoSetterList.get("int16").setValue(pojo, 22); - - Assert.assertEquals(pojo.getString(), "John Doe"); - Assert.assertEquals(pojo.getInt32(), 30); - Assert.assertEquals(pojo.getInt16(), 22); - } public static class SamplePOJOInt256Setter implements POJOSetter { - - - - /* - public void setValue(java.lang.Object, java.lang.Object); - Code: - 0: aload_1 - 1: checkcast #7 // class com/clickhouse/client/query/SamplePOJO - 4: aload_2 - 5: checkcast #25 // class java/math/BigInteger - 8: invokevirtual #27 // Method com/clickhouse/client/query/SamplePOJO.setInt256:(Ljava/math/BigInteger;)V - 11: return - */ @Override - public void setValue(Object obj, Object value) { - Arrays.stream(((Object[]) value)).collect(Collectors.toList()); + public void setValue(Object obj, BinaryStreamReader reader, ClickHouseColumn column) throws IOException { + ((QuerySamplePOJO)obj).setDateTime(((ZonedDateTime)reader.readValue(column)).toLocalDateTime()); + } + + public void readValue(Object obj, BinaryStreamReader reader, ClickHouseColumn column) throws IOException { +// ((SamplePOJO)obj).setDateTime(((ZonedDateTime)reader.readValue(column)).toLocalDateTime()); + ((SimplePOJO)obj).setId(reader.readIntLE()); } } } diff --git a/client-v2/src/test/java/com/clickhouse/client/query/SamplePOJO.java b/client-v2/src/test/java/com/clickhouse/client/query/QuerySamplePOJO.java similarity index 83% rename from client-v2/src/test/java/com/clickhouse/client/query/SamplePOJO.java rename to client-v2/src/test/java/com/clickhouse/client/query/QuerySamplePOJO.java index 024296410..dc1493bac 100644 --- a/client-v2/src/test/java/com/clickhouse/client/query/SamplePOJO.java +++ b/client-v2/src/test/java/com/clickhouse/client/query/QuerySamplePOJO.java @@ -18,7 +18,7 @@ import java.util.Random; import java.util.UUID; -public class SamplePOJO { +public class QuerySamplePOJO { private int int8; private int int8_default; private int int16; @@ -35,7 +35,7 @@ public class SamplePOJO { private int uint8; private int uint16; private long uint32; - private long uint64; + private BigInteger uint64; private BigInteger uint128; private BigInteger uint256; @@ -67,12 +67,12 @@ public class SamplePOJO { private Inet6Address ipv6; private List array; - private List tuple; +// private List tuple; private Map map; private List nestedInnerInt; private List nestedInnerString; - public SamplePOJO() { + public QuerySamplePOJO() { final Random random = new Random(); int8 = random.nextInt(128); int16 = random.nextInt(32768); @@ -90,11 +90,16 @@ public SamplePOJO() { int256 = upper1.or(upper2).or(lower1).or(lower2); + uint8 = random.nextInt(255); uint16 = random.nextInt(32768); uint32 = (long) (random.nextDouble() * 4294967295L); - uint64 = (long) (random.nextDouble() * 18446744073709615L); + long rndUInt64 = random.nextLong(); + uint64 = BigInteger.valueOf(rndUInt64); + if (rndUInt64 < 0) { + uint64 = uint64.add(BigInteger.ONE.shiftLeft(64)); + } uint128 = upper.or(lower).abs(); uint256 = upper1.or(upper2).or(lower1).or(lower2).abs(); @@ -137,7 +142,7 @@ public SamplePOJO() { } array = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"); - tuple = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +// tuple = Arrays.asList(new Object[]{random.nextInt(), random.nextDouble(), "a", "b" }); map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put(String.valueOf((char) ('a' + i)), i + 1); @@ -248,11 +253,11 @@ public void setUint32(long uint32) { this.uint32 = uint32; } - public long getUint64() { + public BigInteger getUint64() { return uint64; } - public void setUint64(long uint64) { + public void setUint64(BigInteger uint64) { this.uint64 = uint64; } @@ -424,13 +429,13 @@ public void setArray(List array) { this.array = array; } - public List getTuple() { - return tuple; - } +// public List getTuple() { +// return tuple; +// } - public void setTuple(List tuple) { - this.tuple = tuple; - } +// public void setTuple(List tuple) { +// this.tuple = tuple; +// } public Map getMap() { return map; @@ -460,18 +465,18 @@ public void setNestedInnerString(List nestedInnerString) { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - SamplePOJO that = (SamplePOJO) o; - return int8 == that.int8 && int16 == that.int16 && int32 == that.int32 && int64 == that.int64 && uint8 == that.uint8 && uint16 == that.uint16 && uint32 == that.uint32 && uint64 == that.uint64 && Float.compare(float32, that.float32) == 0 && Double.compare(float64, that.float64) == 0 && bool == that.bool && enum8 == that.enum8 && enum16 == that.enum16 && Objects.equals(int128, that.int128) && Objects.equals(int256, that.int256) && Objects.equals(uint128, that.uint128) && Objects.equals(uint256, that.uint256) && Objects.equals(decimal32, that.decimal32) && Objects.equals(decimal64, that.decimal64) && Objects.equals(decimal128, that.decimal128) && Objects.equals(decimal256, that.decimal256) && Objects.equals(string, that.string) && Objects.equals(fixedString, that.fixedString) && Objects.equals(date, that.date) && Objects.equals(date32, that.date32) && Objects.equals(dateTime, that.dateTime) && Objects.equals(dateTime64, that.dateTime64) && Objects.equals(uuid, that.uuid) && Objects.equals(ipv4, that.ipv4) && Objects.equals(ipv6, that.ipv6) && Objects.equals(array, that.array) && Objects.equals(tuple, that.tuple) && Objects.equals(map, that.map) && Objects.equals(nestedInnerInt, that.nestedInnerInt) && Objects.equals(nestedInnerString, that.nestedInnerString); + QuerySamplePOJO that = (QuerySamplePOJO) o; + return int8 == that.int8 && int8_default == that.int8_default && int16 == that.int16 && int16_default == that.int16_default && int32 == that.int32 && int32_default == that.int32_default && int64 == that.int64 && int64_default == that.int64_default && uint8 == that.uint8 && uint16 == that.uint16 && uint32 == that.uint32 && Float.compare(float32, that.float32) == 0 && Double.compare(float64, that.float64) == 0 && bool == that.bool && enum8 == that.enum8 && enum16 == that.enum16 && Objects.equals(int128, that.int128) && Objects.equals(int128_default, that.int128_default) && Objects.equals(int256, that.int256) && Objects.equals(int256_default, that.int256_default) && Objects.equals(uint64, that.uint64) && Objects.equals(uint128, that.uint128) && Objects.equals(uint256, that.uint256) && Objects.equals(decimal32, that.decimal32) && Objects.equals(decimal64, that.decimal64) && Objects.equals(decimal128, that.decimal128) && Objects.equals(decimal256, that.decimal256) && Objects.equals(string, that.string) && Objects.equals(fixedString, that.fixedString) && Objects.equals(date, that.date) && Objects.equals(date32, that.date32) && Objects.equals(dateTime, that.dateTime) && Objects.equals(dateTime64, that.dateTime64) && Objects.equals(uuid, that.uuid) && Objects.equals(ipv4, that.ipv4) && Objects.equals(ipv6, that.ipv6) && Objects.equals(array, that.array) && Objects.equals(map, that.map) && Objects.equals(nestedInnerInt, that.nestedInnerInt) && Objects.equals(nestedInnerString, that.nestedInnerString); } @Override public int hashCode() { - return Objects.hash(int8, int16, int32, int64, int128, int256, uint8, uint16, uint32, uint64, uint128, uint256, float32, float64, decimal32, decimal64, decimal128, decimal256, bool, string, fixedString, date, date32, dateTime, dateTime64, uuid, enum8, enum16, ipv4, ipv6, array, tuple, map, nestedInnerInt, nestedInnerString); + return Objects.hash(int8, int8_default, int16, int16_default, int32, int32_default, int64, int64_default, int128, int128_default, int256, int256_default, uint8, uint16, uint32, uint64, uint128, uint256, float32, float64, decimal32, decimal64, decimal128, decimal256, bool, string, fixedString, date, date32, dateTime, dateTime64, uuid, enum8, enum16, ipv4, ipv6, array, map, nestedInnerInt, nestedInnerString); } @Override public String toString() { - return "SamplePOJO{" + + return "QuerySamplePOJO{" + "int8=" + int8 + ", int8_default=" + int8_default + ", int16=" + int16 + @@ -509,7 +514,6 @@ public String toString() { ", ipv4=" + ipv4 + ", ipv6=" + ipv6 + ", array=" + array + - ", tuple=" + tuple + ", map=" + map + ", nestedInnerInt=" + nestedInnerInt + ", nestedInnerString=" + nestedInnerString + @@ -555,7 +559,7 @@ public static String generateTableCreateSQL(String tableName) { "ipv4 IPv4, " + "ipv6 IPv6, " + "array Array(String), " + - "tuple Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32), " + +// "tuple Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32), " + "map Map(String, Int32), " + "nested Nested (innerInt Int32, innerString String)" + ") ENGINE = Memory"; diff --git a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java index ae56e90f1..9a632a9d6 100644 --- a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java @@ -31,7 +31,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; -import org.testcontainers.shaded.com.google.common.collect.Table; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -47,7 +46,6 @@ import java.io.OutputStreamWriter; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.RoundingMode; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -91,6 +89,10 @@ public class QueryTests extends BaseIntegrationTest { private boolean usePreallocatedBuffers = false; + static { +// System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "DEBUG"); + } + QueryTests(){ } @@ -1473,7 +1475,6 @@ public void testQueryReadToPOJO() { " FROM system.numbers LIMIT " + limit; TableSchema schema = client.getTableSchemaFromQuery(sql, "q1"); client.register(SimplePOJO.class, schema); - List pojos = client.queryAll(sql, SimplePOJO.class); Assert.assertEquals(pojos.size(), limit); } @@ -1497,12 +1498,12 @@ public void testQueryReadToPOJOWithoutGetters() { public void testQueryAllWithPOJO() throws Exception { final String tableName = "test_query_all_with_pojo"; - final String createTableSQL = SamplePOJO.generateTableCreateSQL(tableName); + final String createTableSQL = QuerySamplePOJO.generateTableCreateSQL(tableName); client.execute("DROP TABLE IF EXISTS test_query_all_with_pojo").get(); client.execute(createTableSQL).get(); - SamplePOJO pojo = new SamplePOJO(); - client.register(SamplePOJO.class, client.getTableSchema(tableName)); + QuerySamplePOJO pojo = new QuerySamplePOJO(); + client.register(QuerySamplePOJO.class, client.getTableSchema(tableName)); client.insert(tableName, Collections.singletonList(pojo)).get(); @@ -1516,7 +1517,7 @@ public void testQueryAllWithPOJO() throws Exception { pojo.setDateTime(pojo.getDateTime().minusNanos(pojo.getDateTime().getNano())); pojo.setDateTime64(pojo.getDateTime64().withNano((int) Math.ceil((pojo.getDateTime64().getNano() / 1000_000) * 1000_000))); - List pojos = client.queryAll("SELECT * FROM " + tableName + " LIMIT 1", SamplePOJO.class); + List pojos = client.queryAll("SELECT * FROM " + tableName + " LIMIT 1", QuerySamplePOJO.class); Assert.assertEquals(pojos.get(0), pojo, "Expected " + pojo + " but got " + pojos.get(0)); }