From 9ffd9a4108bc9d60be36d28e25c03acc305f4a5a Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 10 Apr 2026 15:20:43 -0700 Subject: [PATCH 01/26] Implemented JSONEachrow support in client-v2 --- client-v2/pom.xml | 20 +- .../com/clickhouse/client/api/Client.java | 8 + .../client/api/ClientConfigProperties.java | 9 + .../data_formats/JSONEachRowFormatReader.java | 528 ++++++++++++++++++ .../data_formats/internal/GsonJsonParser.java | 39 ++ .../internal/JacksonJsonParser.java | 49 ++ .../api/data_formats/internal/JsonParser.java | 15 + .../internal/JsonParserFactory.java | 27 + .../AbstractJSONEachRowFormatReaderTest.java | 124 ++++ .../GsonJSONEachRowFormatReaderTest.java | 8 + .../JacksonJSONEachRowFormatReaderTest.java | 8 + jdbc-v2/pom.xml | 39 +- .../com/clickhouse/jdbc/DriverProperties.java | 10 +- .../com/clickhouse/jdbc/StatementImpl.java | 10 +- .../jdbc/internal/JdbcConfiguration.java | 2 + .../com/clickhouse/jdbc/StatementTest.java | 32 ++ 16 files changed, 908 insertions(+), 20 deletions(-) create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java diff --git a/client-v2/pom.xml b/client-v2/pom.xml index 0c6409cdf..57ff943a8 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -88,8 +88,26 @@ com.fasterxml.jackson.core jackson-databind - test ${jackson.version} + provided + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + provided + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + provided + + + com.google.code.gson + gson + ${gson.version} + provided ${project.parent.groupId} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index d4f979026..d4e43ef22 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -3,11 +3,14 @@ import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.command.CommandSettings; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; import com.clickhouse.client.api.data_formats.NativeFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryWithNamesFormatReader; import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; +import com.clickhouse.client.api.data_formats.internal.JsonParser; +import com.clickhouse.client.api.data_formats.internal.JsonParserFactory; import com.clickhouse.client.api.data_formats.internal.MapBackedRecord; import com.clickhouse.client.api.data_formats.internal.ProcessParser; import com.clickhouse.client.api.enums.Protocol; @@ -2077,6 +2080,11 @@ public ClickHouseBinaryFormatReader newBinaryFormatReader(QueryResponse response reader = new RowBinaryFormatReader(response.getInputStream(), response.getSettings(), schema, byteBufferPool, typeHintMapping); break; + case JSONEachRow: + String jsonProcessor = ClientConfigProperties.JSON_PROCESSOR.getOrDefault(configuration); + JsonParser parser = JsonParserFactory.createParser(jsonProcessor, response.getInputStream()); + reader = new JSONEachRowFormatReader(parser); + break; default: throw new IllegalArgumentException("Binary readers doesn't support format: " + response.getFormat()); } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java index e548a90f9..9c40e48dd 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java @@ -196,6 +196,15 @@ public Object parseValue(String value) { * See ClickHouse Docs */ CUSTOM_SETTINGS_PREFIX("custom_settings_prefix", String.class, "custom_"), + + /** + * Configures what JSON processor will be used for JSON formats. Choices: + * + */ + JSON_PROCESSOR("json_processor", String.class, "JACKSON"), ; private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java new file mode 100644 index 000000000..d0615e526 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -0,0 +1,528 @@ +package com.clickhouse.client.api.data_formats; + +import com.clickhouse.client.api.data_formats.internal.JsonParser; +import com.clickhouse.client.api.metadata.TableSchema; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.ClickHouseDataType; +import com.clickhouse.data.value.ClickHouseBitmap; +import com.clickhouse.data.value.ClickHouseGeoMultiPolygonValue; +import com.clickhouse.data.value.ClickHouseGeoPointValue; +import com.clickhouse.data.value.ClickHouseGeoPolygonValue; +import com.clickhouse.data.value.ClickHouseGeoRingValue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAmount; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class JSONEachRowFormatReader implements ClickHouseBinaryFormatReader { + private final JsonParser parser; + private TableSchema schema; + private Map currentRow; + private Map firstRow; + private boolean firstRowRead = false; + + public JSONEachRowFormatReader(JsonParser parser) { + this.parser = parser; + try { + this.firstRow = parser.nextRow(); + if (firstRow != null) { + List columns = new ArrayList<>(); + for (String key : firstRow.keySet()) { + // For JSONEachRow we don't know the exact ClickHouse type, so we use a reasonable default. + // We can try to guess based on the value type in the first row. + columns.add(ClickHouseColumn.of(key, guessDataType(firstRow.get(key)), false)); + } + this.schema = new TableSchema(columns); + } else { + this.schema = new TableSchema(new ArrayList<>()); + } + } catch (Exception e) { + throw new RuntimeException("Failed to initialize JSON reader", e); + } + } + + private ClickHouseDataType guessDataType(Object value) { + if (value instanceof Number) { + if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) { + return ClickHouseDataType.Int64; + } else if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) { + double d = ((Number) value).doubleValue(); + if (d == Math.floor(d) && !Double.isInfinite(d) && d <= Long.MAX_VALUE && d >= Long.MIN_VALUE) { + return ClickHouseDataType.Int64; + } + return ClickHouseDataType.Float64; + } else { + return ClickHouseDataType.Float64; + } + } else if (value instanceof Boolean) { + return ClickHouseDataType.Bool; + } else { + return ClickHouseDataType.String; + } + } + + @Override + public T readValue(int colIndex) { + return (T) currentRow.get(schema.columnIndexToName(colIndex)); + } + + @Override + public T readValue(String colName) { + return (T) currentRow.get(colName); + } + + @Override + public boolean hasValue(String colName) { + return currentRow.containsKey(colName) && currentRow.get(colName) != null; + } + + @Override + public boolean hasValue(int colIndex) { + return hasValue(schema.columnIndexToName(colIndex)); + } + + @Override + public boolean hasNext() { + if (!firstRowRead) { + return firstRow != null; + } + return true; // We'll find out in next() + } + + @Override + public Map next() { + if (!firstRowRead) { + firstRowRead = true; + currentRow = firstRow; + return currentRow; + } + try { + currentRow = parser.nextRow(); + return currentRow; + } catch (Exception e) { + throw new RuntimeException("Failed to read next JSON row", e); + } + } + + @Override + public String getString(String colName) { + Object val = currentRow.get(colName); + return val == null ? null : val.toString(); + } + + @Override + public byte getByte(String colName) { + return ((Number) currentRow.get(colName)).byteValue(); + } + + @Override + public short getShort(String colName) { + return ((Number) currentRow.get(colName)).shortValue(); + } + + @Override + public int getInteger(String colName) { + return ((Number) currentRow.get(colName)).intValue(); + } + + @Override + public long getLong(String colName) { + return ((Number) currentRow.get(colName)).longValue(); + } + + @Override + public float getFloat(String colName) { + return ((Number) currentRow.get(colName)).floatValue(); + } + + @Override + public double getDouble(String colName) { + return ((Number) currentRow.get(colName)).doubleValue(); + } + + @Override + public boolean getBoolean(String colName) { + Object val = currentRow.get(colName); + if (val instanceof Boolean) return (Boolean) val; + if (val instanceof Number) return ((Number) val).intValue() != 0; + return Boolean.parseBoolean(val.toString()); + } + + @Override + public BigInteger getBigInteger(String colName) { + Object val = currentRow.get(colName); + if (val == null) return null; + if (val instanceof BigInteger) return (BigInteger) val; + return new BigDecimal(val.toString()).toBigInteger(); + } + + @Override + public BigDecimal getBigDecimal(String colName) { + Object val = currentRow.get(colName); + if (val instanceof BigDecimal) return (BigDecimal) val; + return new BigDecimal(val.toString()); + } + + @Override + public Instant getInstant(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public ZonedDateTime getZonedDateTime(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public Duration getDuration(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public Inet4Address getInet4Address(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public Inet6Address getInet6Address(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public UUID getUUID(String colName) { + return UUID.fromString(currentRow.get(colName).toString()); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public List getList(String colName) { + return (List) currentRow.get(colName); + } + + @Override + public byte[] getByteArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public long[] getLongArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public float[] getFloatArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public double[] getDoubleArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean[] getBooleanArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public short[] getShortArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getStringArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] getObjectArray(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public String getString(int index) { + return getString(schema.columnIndexToName(index)); + } + + @Override + public byte getByte(int index) { + return getByte(schema.columnIndexToName(index)); + } + + @Override + public short getShort(int index) { + return getShort(schema.columnIndexToName(index)); + } + + @Override + public int getInteger(int index) { + return getInteger(schema.columnIndexToName(index)); + } + + @Override + public long getLong(int index) { + return getLong(schema.columnIndexToName(index)); + } + + @Override + public float getFloat(int index) { + return getFloat(schema.columnIndexToName(index)); + } + + @Override + public double getDouble(int index) { + return getDouble(schema.columnIndexToName(index)); + } + + @Override + public boolean getBoolean(int index) { + return getBoolean(schema.columnIndexToName(index)); + } + + @Override + public BigInteger getBigInteger(int index) { + return getBigInteger(schema.columnIndexToName(index)); + } + + @Override + public BigDecimal getBigDecimal(int index) { + return getBigDecimal(schema.columnIndexToName(index)); + } + + @Override + public Instant getInstant(int index) { + return getInstant(schema.columnIndexToName(index)); + } + + @Override + public ZonedDateTime getZonedDateTime(int index) { + return getZonedDateTime(schema.columnIndexToName(index)); + } + + @Override + public Duration getDuration(int index) { + return getDuration(schema.columnIndexToName(index)); + } + + @Override + public Inet4Address getInet4Address(int index) { + return getInet4Address(schema.columnIndexToName(index)); + } + + @Override + public Inet6Address getInet6Address(int index) { + return getInet6Address(schema.columnIndexToName(index)); + } + + @Override + public UUID getUUID(int index) { + return getUUID(schema.columnIndexToName(index)); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(int index) { + return getGeoPoint(schema.columnIndexToName(index)); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(int index) { + return getGeoRing(schema.columnIndexToName(index)); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(int index) { + return getGeoPolygon(schema.columnIndexToName(index)); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index) { + return getGeoMultiPolygon(schema.columnIndexToName(index)); + } + + @Override + public List getList(int index) { + return getList(schema.columnIndexToName(index)); + } + + @Override + public byte[] getByteArray(int index) { + return getByteArray(schema.columnIndexToName(index)); + } + + @Override + public int[] getIntArray(int index) { + return getIntArray(schema.columnIndexToName(index)); + } + + @Override + public long[] getLongArray(int index) { + return getLongArray(schema.columnIndexToName(index)); + } + + @Override + public float[] getFloatArray(int index) { + return getFloatArray(schema.columnIndexToName(index)); + } + + @Override + public double[] getDoubleArray(int index) { + return getDoubleArray(schema.columnIndexToName(index)); + } + + @Override + public boolean[] getBooleanArray(int index) { + return getBooleanArray(schema.columnIndexToName(index)); + } + + @Override + public short[] getShortArray(int index) { + return getShortArray(schema.columnIndexToName(index)); + } + + @Override + public String[] getStringArray(int index) { + return getStringArray(schema.columnIndexToName(index)); + } + + @Override + public Object[] getObjectArray(int index) { + return getObjectArray(schema.columnIndexToName(index)); + } + + @Override + public Object[] getTuple(int index) { + return getTuple(schema.columnIndexToName(index)); + } + + @Override + public Object[] getTuple(String colName) { + return (Object[]) currentRow.get(colName); + } + + @Override + public byte getEnum8(String colName) { + return getByte(colName); + } + + @Override + public byte getEnum8(int index) { + return getByte(index); + } + + @Override + public short getEnum16(String colName) { + return getShort(colName); + } + + @Override + public short getEnum16(int index) { + return getShort(index); + } + + @Override + public LocalDate getLocalDate(String colName) { + return LocalDate.parse(currentRow.get(colName).toString()); + } + + @Override + public LocalDate getLocalDate(int index) { + return getLocalDate(schema.columnIndexToName(index)); + } + + @Override + public LocalTime getLocalTime(String colName) { + return LocalTime.parse(currentRow.get(colName).toString()); + } + + @Override + public LocalTime getLocalTime(int index) { + return getLocalTime(schema.columnIndexToName(index)); + } + + @Override + public LocalDateTime getLocalDateTime(String colName) { + return LocalDateTime.parse(currentRow.get(colName).toString()); + } + + @Override + public LocalDateTime getLocalDateTime(int index) { + return getLocalDateTime(schema.columnIndexToName(index)); + } + + @Override + public OffsetDateTime getOffsetDateTime(String colName) { + return OffsetDateTime.parse(currentRow.get(colName).toString()); + } + + @Override + public OffsetDateTime getOffsetDateTime(int index) { + return getOffsetDateTime(schema.columnIndexToName(index)); + } + + @Override + public TableSchema getSchema() { + return schema; + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public TemporalAmount getTemporalAmount(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public TemporalAmount getTemporalAmount(String colName) { + throw new UnsupportedOperationException(); + } + + @Override + public void close() throws Exception { + parser.close(); + } +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java new file mode 100644 index 000000000..fe70ec93f --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java @@ -0,0 +1,39 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class GsonJsonParser implements JsonParser { + private final Gson gson; + private final JsonReader reader; + + public GsonJsonParser(InputStream inputStream) { + this.gson = new Gson(); + this.reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + this.reader.setLenient(true); // JSONEachRow needs lenient reader for multiple root objects + } + + @Override + public Map nextRow() throws Exception { + try { + if (reader.peek() == JsonToken.END_DOCUMENT) { + return null; + } + } catch (java.io.EOFException e) { + return null; + } + return gson.fromJson(reader, new TypeToken>() {}.getType()); + } + + @Override + public void close() throws Exception { + reader.close(); + } +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java new file mode 100644 index 000000000..e406ba5cc --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java @@ -0,0 +1,49 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.InputStream; +import java.util.Map; + +public class JacksonJsonParser implements com.clickhouse.client.api.data_formats.internal.JsonParser { + private final ObjectMapper mapper; + private final JsonFactory factory; + private com.fasterxml.jackson.core.JsonParser parser; + + public JacksonJsonParser(InputStream inputStream) { + this.mapper = new ObjectMapper(); + this.factory = new JsonFactory(); + try { + this.parser = factory.createParser(inputStream); + } catch (Exception e) { + throw new RuntimeException("Failed to create Jackson parser", e); + } + } + + @Override + public Map nextRow() throws Exception { + if (parser.nextToken() == null) { + return null; + } + if (parser.currentToken() != JsonToken.START_OBJECT) { + // Handle cases where there might be extra characters between objects, + // like newlines in JSONEachRow. + while (parser.nextToken() != null && parser.currentToken() != JsonToken.START_OBJECT) { + // skip + } + if (parser.currentToken() == null) { + return null; + } + } + return mapper.readValue(parser, Map.class); + } + + @Override + public void close() throws Exception { + if (parser != null) { + parser.close(); + } + } +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java new file mode 100644 index 000000000..2d02dabb3 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java @@ -0,0 +1,15 @@ +package com.clickhouse.client.api.data_formats.internal; + +import java.util.Map; + +/** + * Interface for JSON row processors. + */ +public interface JsonParser extends AutoCloseable { + /** + * Reads next row from the input stream. + * @return map of column names to values, or null if no more rows + * @throws Exception if an error occurs during parsing + */ + Map nextRow() throws Exception; +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java new file mode 100644 index 000000000..0e86fd70a --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java @@ -0,0 +1,27 @@ +package com.clickhouse.client.api.data_formats.internal; + +import java.io.InputStream; +import java.lang.reflect.Constructor; + +public class JsonParserFactory { + public static JsonParser createParser(String type, InputStream inputStream) { + String className; + if ("JACKSON".equalsIgnoreCase(type)) { + className = "com.clickhouse.client.api.data_formats.internal.JacksonJsonParser"; + } else if ("GSON".equalsIgnoreCase(type)) { + className = "com.clickhouse.client.api.data_formats.internal.GsonJsonParser"; + } else { + throw new IllegalArgumentException("Unsupported JSON processor: " + type + ". Supported: JACKSON, GSON"); + } + + try { + Class clazz = Class.forName(className); + Constructor constructor = clazz.getConstructor(InputStream.class); + return (JsonParser) constructor.newInstance(inputStream); + } catch (ClassNotFoundException e) { + throw new RuntimeException("JSON processor class not found: " + className + ". Make sure you have the required library (Jackson or Gson) on your classpath.", e); + } catch (Exception e) { + throw new RuntimeException("Failed to instantiate JSON processor: " + type, e); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java new file mode 100644 index 000000000..223f05089 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java @@ -0,0 +1,124 @@ +package com.clickhouse.client.api.data_formats; + +import com.clickhouse.client.api.data_formats.internal.JsonParser; +import com.clickhouse.client.api.data_formats.internal.JsonParserFactory; +import com.clickhouse.data.ClickHouseDataType; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public abstract class AbstractJSONEachRowFormatReaderTest { + + protected abstract String getProcessor(); + + @Test + public void testBasicParsing() throws Exception { + String json = "{\"id\":1,\"name\":\"test\",\"active\":true}\n" + + "{\"id\":2,\"name\":\"clickhouse\",\"active\":false}"; + + try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + + // First row + Assert.assertTrue(reader.hasNext()); + Map row1 = reader.next(); + Assert.assertNotNull(row1); + Assert.assertEquals(reader.getInteger("id"), 1); + Assert.assertEquals(reader.getString("name"), "test"); + Assert.assertEquals(reader.getBoolean("active"), true); + + // Second row + Assert.assertTrue(reader.hasNext()); + Map row2 = reader.next(); + Assert.assertNotNull(row2); + Assert.assertEquals(reader.getInteger("id"), 2); + Assert.assertEquals(reader.getString("name"), "clickhouse"); + Assert.assertEquals(reader.getBoolean("active"), false); + + // No more rows + Assert.assertNull(reader.next()); + } + } + + @Test + public void testSchemaInference() throws Exception { + String json = "{\"col_int\":42,\"col_float\":3.14,\"col_bool\":true,\"col_str\":\"val\"}"; + + try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + + Assert.assertNotNull(reader.getSchema()); + Assert.assertEquals(reader.getSchema().getColumns().size(), 4); + + Assert.assertEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.Float64); + Assert.assertEquals(reader.getSchema().getColumnByIndex(3).getDataType(), ClickHouseDataType.Bool); + Assert.assertEquals(reader.getSchema().getColumnByIndex(4).getDataType(), ClickHouseDataType.String); + } + } + + @Test + public void testDataTypes() throws Exception { + String json = "{\"b\":120,\"s\":30000,\"i\":1000000,\"l\":10000000000,\"f\":1.23,\"d\":1.23456789,\"bool\":true,\"str\":\"hello\"}"; + + try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + + reader.next(); + Assert.assertEquals(reader.getByte("b"), (byte) 120); + Assert.assertEquals(reader.getShort("s"), (short) 30000); + Assert.assertEquals(reader.getInteger("i"), 1000000); + Assert.assertEquals(reader.getLong("l"), 10000000000L); + Assert.assertEquals(reader.getFloat("f"), 1.23f, 0.001f); + Assert.assertEquals(reader.getDouble("d"), 1.23456789d, 0.00000001d); + Assert.assertEquals(reader.getBoolean("bool"), true); + Assert.assertEquals(reader.getString("str"), "hello"); + + Assert.assertEquals(reader.getBigInteger("l"), BigInteger.valueOf(10000000000L)); + Assert.assertEquals(reader.getBigDecimal("d").doubleValue(), 1.23456789d, 0.00000001d); + } + } + + @Test + public void testEmptyData() throws Exception { + String json = ""; + + try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + + Assert.assertFalse(reader.hasNext()); + Assert.assertNull(reader.next()); + Assert.assertEquals(reader.getSchema().getColumns().size(), 0); + } + } + + @Test + public void testMixedNewlines() throws Exception { + // JSONEachRow often has newlines between objects + String json = "{\"id\":1}\n\n\r\n{\"id\":2}"; + + try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + + Assert.assertTrue(reader.hasNext()); + reader.next(); + Assert.assertEquals(reader.getInteger("id"), 1); + + reader.next(); + Assert.assertEquals(reader.getInteger("id"), 2); + + Assert.assertNull(reader.next()); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java new file mode 100644 index 000000000..d3d3e63ce --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java @@ -0,0 +1,8 @@ +package com.clickhouse.client.api.data_formats; + +public class GsonJSONEachRowFormatReaderTest extends AbstractJSONEachRowFormatReaderTest { + @Override + protected String getProcessor() { + return "GSON"; + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java new file mode 100644 index 000000000..350d1bd08 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java @@ -0,0 +1,8 @@ +package com.clickhouse.client.api.data_formats; + +public class JacksonJSONEachRowFormatReaderTest extends AbstractJSONEachRowFormatReaderTest { + @Override + protected String getProcessor() { + return "JACKSON"; + } +} diff --git a/jdbc-v2/pom.xml b/jdbc-v2/pom.xml index a8f02f2e9..05b035694 100644 --- a/jdbc-v2/pom.xml +++ b/jdbc-v2/pom.xml @@ -50,14 +50,35 @@ ${guava.version} - com.fasterxml.jackson.core jackson-databind - test ${jackson.version} + provided + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + provided + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + provided + + + + com.google.code.gson + gson + ${gson.version} + provided + + + ${project.parent.groupId} clickhouse-client @@ -89,18 +110,6 @@ test - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - test - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - com.fasterxml.jackson.dataformat jackson-dataformat-yaml @@ -211,4 +220,4 @@ - \ No newline at end of file + diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 7027c95c1..bb6d4ce08 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -78,9 +78,17 @@ public enum DriverProperties { */ QUERY_ID_GENERATOR("jdbc_query_id_generator", null), + /** + * Configures what JSON processor will be used for JSON formats. Choices: + * + */ + JSON_PROCESSOR("json_processor", "JACKSON", Arrays.asList("JACKSON", "GSON")), + /** * Controls logic of saving roles that were set using {@code SET } statement. - * Default: true - save roles */ REMEMBER_LAST_SET_ROLES("remember_last_set_roles", String.valueOf(Boolean.TRUE)), diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index f50546393..59949c6f5 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -6,6 +6,7 @@ import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.client.api.sql.SQLUtils; +import com.clickhouse.data.ClickHouseFormat; import com.clickhouse.jdbc.internal.ExceptionUtils; import com.clickhouse.jdbc.internal.FeatureManager; import com.clickhouse.jdbc.internal.ParsedStatement; @@ -177,11 +178,14 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr response = connection.getClient().query(lastStatementSql, mergedSettings).get(queryTimeout, TimeUnit.SECONDS); } - if (response.getFormat().isText()) { - throw new SQLException("Only RowBinaryWithNameAndTypes is supported for output format. Please check your query.", + ClickHouseBinaryFormatReader reader; + if (response.getFormat() == ClickHouseFormat.JSONEachRow || !response.getFormat().isText()) { + reader = connection.getClient().newBinaryFormatReader(response); + } else { + throw new SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported for output format. Please check your query.", ExceptionUtils.SQL_STATE_CLIENT_ERROR); } - ClickHouseBinaryFormatReader reader = connection.getClient().newBinaryFormatReader(response); + if (reader.getSchema() == null) { long writtenRows = 0L; try { diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java index 9aa5ce61a..6fd387297 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java @@ -303,6 +303,8 @@ private void initProperties(Map urlProperties, Properties provid prop.getKey().equalsIgnoreCase(DriverProperties.CUSTOM_SETTINGS.getKey())) { ClientConfigProperties.toKeyValuePairs(prop.getValue()) .forEach((k, v) -> clientProperties.put(ClientConfigProperties.serverSetting(k), v)); + } else if (prop.getKey().equalsIgnoreCase(DriverProperties.JSON_PROCESSOR.getKey())) { + clientProperties.put(prop.getKey(), prop.getValue()); } driverProperties.put(prop.getKey(), prop.getValue()); } else { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 575dde388..f7fdb1295 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -619,6 +619,38 @@ public void testTextFormatInResponse() throws Exception { } } + @Test(groups = {"integration"}) + public void testJSONEachRowFormat() throws Exception { + Properties properties = new Properties(); + properties.setProperty(DriverProperties.JSON_PROCESSOR.getKey(), "JACKSON"); + try (Connection conn = getJdbcConnection(properties)) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str FORMAT JSONEachRow")) { + assertTrue(rs.next()); + assertEquals(rs.getInt("num"), 1); + assertEquals(rs.getString("str"), "test"); + assertFalse(rs.next()); + } + } + } + } + + @Test(groups = {"integration"}) + public void testJSONEachRowFormatGson() throws Exception { + Properties properties = new Properties(); + properties.setProperty(DriverProperties.JSON_PROCESSOR.getKey(), "GSON"); + try (Connection conn = getJdbcConnection(properties)) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT 2 AS num, 'gson' AS str FORMAT JSONEachRow")) { + assertTrue(rs.next()); + assertEquals(rs.getInt("num"), 2); + assertEquals(rs.getString("str"), "gson"); + assertFalse(rs.next()); + } + } + } + } + @Test(groups = "integration") void testWithClause() throws Exception { int count = 0; From 090fb855762fd081390e75e41cee40022774673b Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 10 Apr 2026 16:08:58 -0700 Subject: [PATCH 02/26] Added primitive tests for JSON each row --- .../AbstractJSONEachRowFormatReaderTest.java | 124 ----------------- .../AbstractJSONEachRowFormatReaderTests.java | 128 ++++++++++++++++++ .../GsonJSONEachRowFormatReaderTest.java | 8 -- .../GsonJSONEachRowFormatReaderTests.java | 11 ++ .../JacksonJSONEachRowFormatReaderTest.java | 8 -- .../JacksonJSONEachRowFormatReaderTests.java | 13 ++ 6 files changed, 152 insertions(+), 140 deletions(-) delete mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java delete mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java delete mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java deleted file mode 100644 index 223f05089..000000000 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.clickhouse.client.api.data_formats; - -import com.clickhouse.client.api.data_formats.internal.JsonParser; -import com.clickhouse.client.api.data_formats.internal.JsonParserFactory; -import com.clickhouse.data.ClickHouseDataType; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -public abstract class AbstractJSONEachRowFormatReaderTest { - - protected abstract String getProcessor(); - - @Test - public void testBasicParsing() throws Exception { - String json = "{\"id\":1,\"name\":\"test\",\"active\":true}\n" + - "{\"id\":2,\"name\":\"clickhouse\",\"active\":false}"; - - try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); - JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - - // First row - Assert.assertTrue(reader.hasNext()); - Map row1 = reader.next(); - Assert.assertNotNull(row1); - Assert.assertEquals(reader.getInteger("id"), 1); - Assert.assertEquals(reader.getString("name"), "test"); - Assert.assertEquals(reader.getBoolean("active"), true); - - // Second row - Assert.assertTrue(reader.hasNext()); - Map row2 = reader.next(); - Assert.assertNotNull(row2); - Assert.assertEquals(reader.getInteger("id"), 2); - Assert.assertEquals(reader.getString("name"), "clickhouse"); - Assert.assertEquals(reader.getBoolean("active"), false); - - // No more rows - Assert.assertNull(reader.next()); - } - } - - @Test - public void testSchemaInference() throws Exception { - String json = "{\"col_int\":42,\"col_float\":3.14,\"col_bool\":true,\"col_str\":\"val\"}"; - - try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); - JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - - Assert.assertNotNull(reader.getSchema()); - Assert.assertEquals(reader.getSchema().getColumns().size(), 4); - - Assert.assertEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.Int64); - Assert.assertEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.Float64); - Assert.assertEquals(reader.getSchema().getColumnByIndex(3).getDataType(), ClickHouseDataType.Bool); - Assert.assertEquals(reader.getSchema().getColumnByIndex(4).getDataType(), ClickHouseDataType.String); - } - } - - @Test - public void testDataTypes() throws Exception { - String json = "{\"b\":120,\"s\":30000,\"i\":1000000,\"l\":10000000000,\"f\":1.23,\"d\":1.23456789,\"bool\":true,\"str\":\"hello\"}"; - - try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); - JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - - reader.next(); - Assert.assertEquals(reader.getByte("b"), (byte) 120); - Assert.assertEquals(reader.getShort("s"), (short) 30000); - Assert.assertEquals(reader.getInteger("i"), 1000000); - Assert.assertEquals(reader.getLong("l"), 10000000000L); - Assert.assertEquals(reader.getFloat("f"), 1.23f, 0.001f); - Assert.assertEquals(reader.getDouble("d"), 1.23456789d, 0.00000001d); - Assert.assertEquals(reader.getBoolean("bool"), true); - Assert.assertEquals(reader.getString("str"), "hello"); - - Assert.assertEquals(reader.getBigInteger("l"), BigInteger.valueOf(10000000000L)); - Assert.assertEquals(reader.getBigDecimal("d").doubleValue(), 1.23456789d, 0.00000001d); - } - } - - @Test - public void testEmptyData() throws Exception { - String json = ""; - - try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); - JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - - Assert.assertFalse(reader.hasNext()); - Assert.assertNull(reader.next()); - Assert.assertEquals(reader.getSchema().getColumns().size(), 0); - } - } - - @Test - public void testMixedNewlines() throws Exception { - // JSONEachRow often has newlines between objects - String json = "{\"id\":1}\n\n\r\n{\"id\":2}"; - - try (InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - JsonParser parser = JsonParserFactory.createParser(getProcessor(), in); - JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - - Assert.assertTrue(reader.hasNext()); - reader.next(); - Assert.assertEquals(reader.getInteger("id"), 1); - - reader.next(); - Assert.assertEquals(reader.getInteger("id"), 2); - - Assert.assertNull(reader.next()); - } - } -} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java new file mode 100644 index 000000000..41c1398ae --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -0,0 +1,128 @@ +package com.clickhouse.client.api.data_formats; + +import com.clickhouse.client.BaseIntegrationTest; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseServerForTest; +import com.clickhouse.client.api.Client; +import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.enums.Protocol; +import com.clickhouse.client.api.query.QueryResponse; +import com.clickhouse.client.api.query.QuerySettings; +import com.clickhouse.data.ClickHouseDataType; +import com.clickhouse.data.ClickHouseFormat; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Map; + +public abstract class AbstractJSONEachRowFormatReaderTests extends BaseIntegrationTest { + + protected Client client; + + protected abstract String getProcessor(); + + @BeforeMethod(groups = {"integration"}) + public void setUp() { + ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); + client = new Client.Builder() + .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), isCloud()) + .setUsername("default") + .setPassword(ClickHouseServerForTest.getPassword()) + .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), getProcessor()) + .build(); + } + + @AfterMethod(groups = {"integration"}) + public void tearDown() { + if (client != null) { + client.close(); + } + } + + @Test(groups = {"integration"}) + public void testBasicParsing() throws Exception { + String sql = "SELECT 1 as id, 'test' as name, true as active " + + "UNION ALL SELECT 2, 'clickhouse', false " + + "FORMAT JSONEachRow"; + + try (QueryResponse response = client.query(sql).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + // First row + Assert.assertTrue(reader.hasNext()); + Map row1 = reader.next(); + Assert.assertNotNull(row1); + Assert.assertEquals(reader.getInteger("id"), 1); + Assert.assertEquals(reader.getString("name"), "test"); + Assert.assertEquals(reader.getBoolean("active"), true); + + // Second row + Assert.assertTrue(reader.hasNext()); + Map row2 = reader.next(); + Assert.assertNotNull(row2); + Assert.assertEquals(reader.getInteger("id"), 2); + Assert.assertEquals(reader.getString("name"), "clickhouse"); + Assert.assertEquals(reader.getBoolean("active"), false); + + // No more rows + Assert.assertNull(reader.next()); + } + } + + @Test(groups = {"integration"}) + public void testSchemaInference() throws Exception { + String sql = "SELECT toInt64(42) as col_int, toFloat64(3.14) as col_float, " + + "true as col_bool, 'val' as col_str " + + "FORMAT JSONEachRow"; + + try (QueryResponse response = client.query(sql).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + Assert.assertNotNull(reader.getSchema()); + Assert.assertEquals(reader.getSchema().getColumns().size(), 4); + + Assert.assertEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.Float64); + Assert.assertEquals(reader.getSchema().getColumnByIndex(3).getDataType(), ClickHouseDataType.Bool); + Assert.assertEquals(reader.getSchema().getColumnByIndex(4).getDataType(), ClickHouseDataType.String); + } + } + + @Test(groups = {"integration"}) + public void testDataTypes() throws Exception { + String sql = "SELECT toInt8(120) as b, toInt16(30000) as s, toInt32(1000000) as i, " + + "toInt64(10000000000) as l, toFloat32(1.23) as f, toFloat64(1.23456789) as d, " + + "true as bool, 'hello' as str " + + "FORMAT JSONEachRow"; + + try (QueryResponse response = client.query(sql).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + reader.next(); + Assert.assertEquals(reader.getByte("b"), (byte) 120); + Assert.assertEquals(reader.getShort("s"), (short) 30000); + Assert.assertEquals(reader.getInteger("i"), 1000000); + Assert.assertEquals(reader.getLong("l"), 10000000000L); + Assert.assertEquals(reader.getFloat("f"), 1.23f, 0.001f); + Assert.assertEquals(reader.getDouble("d"), 1.23456789d, 0.00000001d); + Assert.assertEquals(reader.getBoolean("bool"), true); + Assert.assertEquals(reader.getString("str"), "hello"); + } + } + + @Test(groups = {"integration"}) + public void testEmptyData() throws Exception { + String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1 FORMAT JSONEachRow"; + + try (QueryResponse response = client.query(sql).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + Assert.assertFalse(reader.hasNext()); + Assert.assertNull(reader.next()); + Assert.assertEquals(reader.getSchema().getColumns().size(), 0); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java deleted file mode 100644 index d3d3e63ce..000000000 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.clickhouse.client.api.data_formats; - -public class GsonJSONEachRowFormatReaderTest extends AbstractJSONEachRowFormatReaderTest { - @Override - protected String getProcessor() { - return "GSON"; - } -} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java new file mode 100644 index 000000000..58573da0c --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java @@ -0,0 +1,11 @@ +package com.clickhouse.client.api.data_formats; + +import org.testng.annotations.Test; + +@Test(groups = {"integration"}) +public class GsonJSONEachRowFormatReaderTests extends AbstractJSONEachRowFormatReaderTests { + @Override + protected String getProcessor() { + return "GSON"; + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java deleted file mode 100644 index 350d1bd08..000000000 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.clickhouse.client.api.data_formats; - -public class JacksonJSONEachRowFormatReaderTest extends AbstractJSONEachRowFormatReaderTest { - @Override - protected String getProcessor() { - return "JACKSON"; - } -} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java new file mode 100644 index 000000000..8689504bb --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java @@ -0,0 +1,13 @@ +package com.clickhouse.client.api.data_formats; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test(groups = {"integration"}) +public class JacksonJSONEachRowFormatReaderTests extends AbstractJSONEachRowFormatReaderTests { + + @Override + protected String getProcessor() { + return "JACKSON"; + } +} From 9d281d9377851d556be6816988a9913f9ae9dc31 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 24 Apr 2026 16:14:01 -0700 Subject: [PATCH 03/26] removed JSON processor from driver properties --- .../src/test/java/com/clickhouse/client/ClientTests.java | 6 +++--- .../main/java/com/clickhouse/jdbc/DriverProperties.java | 9 --------- .../com/clickhouse/jdbc/internal/JdbcConfiguration.java | 9 +-------- .../src/test/java/com/clickhouse/jdbc/StatementTest.java | 4 ++-- .../clickhouse/jdbc/internal/JdbcConfigurationTest.java | 3 ++- 5 files changed, 8 insertions(+), 23 deletions(-) diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 0508d9e58..07154058c 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -329,7 +329,7 @@ public void testDefaultSettings() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. } try (Client client = new Client.Builder() @@ -362,7 +362,7 @@ public void testDefaultSettings() { .setSocketSndbuf(100000) .build()) { Map config = client.getConfiguration(); - Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 36); // to check everything is set. Increment when new added. Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb"); Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10"); Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000"); @@ -429,7 +429,7 @@ public void testWithOldDefaults() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 6ed862bfa..7ec1e6823 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -78,15 +78,6 @@ public enum DriverProperties { */ QUERY_ID_GENERATOR("jdbc_query_id_generator", null), - /** - * Configures what JSON processor will be used for JSON formats. Choices: - *
    - *
  • JACKSON - uses Jackson library.
  • - *
  • GSON - uses Gson library.
  • - *
- */ - JSON_PROCESSOR("json_processor", "JACKSON", Arrays.asList("JACKSON", "GSON")), - /** * Controls logic of saving roles that were set using {@code SET } statement. */ diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java index 779846cc4..a508116b0 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java @@ -18,12 +18,7 @@ import java.nio.charset.StandardCharsets; import java.sql.DriverPropertyInfo; import java.sql.SQLException; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -306,8 +301,6 @@ private void initProperties(Map urlProperties, Properties provid prop.getKey().equalsIgnoreCase(DriverProperties.CUSTOM_SETTINGS.getKey())) { ClientConfigProperties.toKeyValuePairs(prop.getValue()) .forEach((k, v) -> clientProperties.put(ClientConfigProperties.serverSetting(k), v)); - } else if (prop.getKey().equalsIgnoreCase(DriverProperties.JSON_PROCESSOR.getKey())) { - clientProperties.put(prop.getKey(), prop.getValue()); } driverProperties.put(prop.getKey(), prop.getValue()); } else { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index a1587866b..6658a1e3e 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -650,7 +650,7 @@ public void testTextFormatInResponse() throws Exception { @Test(groups = {"integration"}) public void testJSONEachRowFormat() throws Exception { Properties properties = new Properties(); - properties.setProperty(DriverProperties.JSON_PROCESSOR.getKey(), "JACKSON"); + properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); try (Connection conn = getJdbcConnection(properties)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str FORMAT JSONEachRow")) { @@ -666,7 +666,7 @@ public void testJSONEachRowFormat() throws Exception { @Test(groups = {"integration"}) public void testJSONEachRowFormatGson() throws Exception { Properties properties = new Properties(); - properties.setProperty(DriverProperties.JSON_PROCESSOR.getKey(), "GSON"); + properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "GSON"); try (Connection conn = getJdbcConnection(properties)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT 2 AS num, 'gson' AS str FORMAT JSONEachRow")) { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java index a5a3e972f..ef368524e 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java @@ -122,7 +122,8 @@ public void testParseURLValid(String jdbcURL, Properties properties, { JdbcConfiguration configuration = new JdbcConfiguration(jdbcURL, properties); assertEquals(configuration.getConnectionUrl(), connectionURL, "URL: " + jdbcURL); - assertEquals(configuration.clientProperties, expectedClientProps, "URL: " + jdbcURL); + assertEquals(configuration.clientProperties, expectedClientProps, "expected: " + expectedClientProps + + " actual: " + configuration.clientProperties); Client.Builder bob = new Client.Builder(); configuration.applyClientProperties(bob); Client client = bob.build(); From 8337656a132d8e8ac450616a87b19d7295f83859 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 24 Apr 2026 16:38:44 -0700 Subject: [PATCH 04/26] Added examples for json processors --- examples/client-v2-json-processors/.gitignore | 2 + examples/client-v2-json-processors/README.md | 62 +++++++++++++ .../build.gradle.kts | 37 ++++++++ .../gradle.properties | 1 + .../gradle/libs.versions.toml | 12 +++ .../settings.gradle.kts | 5 ++ .../ClientV2JsonProcessorsExample.java | 87 +++++++++++++++++++ examples/jdbc-v2-json-processors/.gitignore | 2 + examples/jdbc-v2-json-processors/README.md | 59 +++++++++++++ .../jdbc-v2-json-processors/build.gradle.kts | 37 ++++++++ .../jdbc-v2-json-processors/gradle.properties | 1 + .../gradle/libs.versions.toml | 12 +++ .../settings.gradle.kts | 5 ++ .../JdbcV2JsonProcessorsExample.java | 64 ++++++++++++++ 14 files changed, 386 insertions(+) create mode 100644 examples/client-v2-json-processors/.gitignore create mode 100644 examples/client-v2-json-processors/README.md create mode 100644 examples/client-v2-json-processors/build.gradle.kts create mode 100644 examples/client-v2-json-processors/gradle.properties create mode 100644 examples/client-v2-json-processors/gradle/libs.versions.toml create mode 100644 examples/client-v2-json-processors/settings.gradle.kts create mode 100644 examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java create mode 100644 examples/jdbc-v2-json-processors/.gitignore create mode 100644 examples/jdbc-v2-json-processors/README.md create mode 100644 examples/jdbc-v2-json-processors/build.gradle.kts create mode 100644 examples/jdbc-v2-json-processors/gradle.properties create mode 100644 examples/jdbc-v2-json-processors/gradle/libs.versions.toml create mode 100644 examples/jdbc-v2-json-processors/settings.gradle.kts create mode 100644 examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java diff --git a/examples/client-v2-json-processors/.gitignore b/examples/client-v2-json-processors/.gitignore new file mode 100644 index 000000000..f8b92c3aa --- /dev/null +++ b/examples/client-v2-json-processors/.gitignore @@ -0,0 +1,2 @@ +.gradle +build diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md new file mode 100644 index 000000000..eb1c12ace --- /dev/null +++ b/examples/client-v2-json-processors/README.md @@ -0,0 +1,62 @@ +# Client V2 JSON Processors Example + +## Overview + +This standalone example shows how to configure `client-v2` to read `JSONEachRow` +responses with either supported JSON processor: + +- `JACKSON` +- `GSON` + +## Requirements + +- JDK 17 or newer +- A running ClickHouse server reachable from the machine running the example +- A locally installed `client-v2` snapshot from this repository + +## How to Run + +From this directory: + +```shell +gradle run +``` + +The example runs both processors by default. To run only one of them: + +```shell +gradle run -DjsonProcessor=GSON +``` + +Connection properties can be supplied as system properties: + +- `-DchEndpoint` - Endpoint to connect to (default: `http://localhost:8123`) +- `-DchUser` - ClickHouse user name (default: `default`) +- `-DchPassword` - ClickHouse user password (default: empty) +- `-DchDatabase` - ClickHouse database name (default: `default`) +- `-DjsonProcessor` - One processor to run: `JACKSON` or `GSON` + +Example with custom connection properties: + +```shell +gradle run \ + -DchEndpoint=http://localhost:8123 \ + -DchUser=default \ + -DchPassword= \ + -DchDatabase=default \ + -DjsonProcessor=JACKSON +``` + +## Executable Example + +`com.clickhouse.examples.client_v2.json_processors.ClientV2JsonProcessorsExample` + +- Creates a `client-v2` instance with `json_processor` set to `JACKSON` or + `GSON`. +- Executes a simple `SELECT ... FROM numbers(3)` query in `JSONEachRow` format. +- Reads rows back through `client.newBinaryFormatReader(response)` and logs the + parsed values. + +The build keeps both `jackson-databind` and `gson` on the classpath so the +example can switch between processors at runtime. Production applications only +need to keep the processor they actually use. diff --git a/examples/client-v2-json-processors/build.gradle.kts b/examples/client-v2-json-processors/build.gradle.kts new file mode 100644 index 000000000..59ec03181 --- /dev/null +++ b/examples/client-v2-json-processors/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + application +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation(libs.clickhouseClient) + + // Keep both processors on the classpath so the example can switch between them. + implementation(libs.jacksonDatabind) + implementation(libs.gson) + + implementation(libs.slf4jApi) + runtimeOnly(libs.slf4jSimple) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +application { + mainClass = "com.clickhouse.examples.client_v2.json_processors.ClientV2JsonProcessorsExample" +} + +tasks.named("run") { + listOf("chEndpoint", "chUser", "chPassword", "chDatabase", "jsonProcessor").forEach { key -> + System.getProperty(key)?.let { value -> + systemProperty(key, value) + } + } +} diff --git a/examples/client-v2-json-processors/gradle.properties b/examples/client-v2-json-processors/gradle.properties new file mode 100644 index 000000000..5ad69748c --- /dev/null +++ b/examples/client-v2-json-processors/gradle.properties @@ -0,0 +1 @@ +org.gradle.configuration-cache=true diff --git a/examples/client-v2-json-processors/gradle/libs.versions.toml b/examples/client-v2-json-processors/gradle/libs.versions.toml new file mode 100644 index 000000000..7379edc0e --- /dev/null +++ b/examples/client-v2-json-processors/gradle/libs.versions.toml @@ -0,0 +1,12 @@ +[versions] +clickhouseClient = "0.9.8-SNAPSHOT" +jackson = "2.18.6" +gson = "2.10.1" +slf4j = "2.0.17" + +[libraries] +clickhouseClient = { module = "com.clickhouse:client-v2", version.ref = "clickhouseClient" } +jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +slf4jApi = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4jSimple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } diff --git a/examples/client-v2-json-processors/settings.gradle.kts b/examples/client-v2-json-processors/settings.gradle.kts new file mode 100644 index 000000000..8b35c469e --- /dev/null +++ b/examples/client-v2-json-processors/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "ch-java-client-v2-json-processors" diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java new file mode 100644 index 000000000..ee4dcb188 --- /dev/null +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -0,0 +1,87 @@ +package com.clickhouse.examples.client_v2.json_processors; + +import com.clickhouse.client.api.Client; +import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.query.QueryResponse; +import com.clickhouse.client.api.query.QuerySettings; +import com.clickhouse.data.ClickHouseFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class ClientV2JsonProcessorsExample { + private static final Logger LOG = LoggerFactory.getLogger(ClientV2JsonProcessorsExample.class); + private static final List SUPPORTED_PROCESSORS = Arrays.asList("JACKSON", "GSON"); + + public static void main(String[] args) throws Exception { + ConnectionConfig config = ConnectionConfig.load(); + + for (String processor : requestedProcessors()) { + runExample(config, processor); + } + } + + private static void runExample(ConnectionConfig config, String processor) throws Exception { + LOG.info("Running client-v2 JSONEachRow example with processor {}", processor); + + try (Client client = new Client.Builder() + .addEndpoint(config.endpoint) + .setUsername(config.user) + .setPassword(config.password) + .setDefaultDatabase(config.database) + // `json_processor` selects the parser used by `newBinaryFormatReader(...)`. + .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), processor) + .build(); + QueryResponse response = client.query( + "SELECT number + 1 AS id, concat('processor-', toString(number + 1)) AS label FROM numbers(3)", + new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + while (reader.hasNext()) { + reader.next(); + LOG.info("[{}] id={}, label={}", processor, reader.getInteger("id"), reader.getString("label")); + } + } + } + + private static List requestedProcessors() { + String requested = System.getProperty("jsonProcessor", "").trim(); + if (requested.isEmpty()) { + return SUPPORTED_PROCESSORS; + } + + String normalized = requested.toUpperCase(Locale.ROOT); + if (!SUPPORTED_PROCESSORS.contains(normalized)) { + throw new IllegalArgumentException("Unsupported jsonProcessor '" + requested + + "'. Expected one of: " + SUPPORTED_PROCESSORS); + } + + return Collections.singletonList(normalized); + } + + private static final class ConnectionConfig { + private final String endpoint; + private final String user; + private final String password; + private final String database; + + private ConnectionConfig(String endpoint, String user, String password, String database) { + this.endpoint = endpoint; + this.user = user; + this.password = password; + this.database = database; + } + + private static ConnectionConfig load() { + return new ConnectionConfig( + System.getProperty("chEndpoint", "http://localhost:8123"), + System.getProperty("chUser", "default"), + System.getProperty("chPassword", ""), + System.getProperty("chDatabase", "default")); + } + } +} diff --git a/examples/jdbc-v2-json-processors/.gitignore b/examples/jdbc-v2-json-processors/.gitignore new file mode 100644 index 000000000..f8b92c3aa --- /dev/null +++ b/examples/jdbc-v2-json-processors/.gitignore @@ -0,0 +1,2 @@ +.gradle +build diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md new file mode 100644 index 000000000..3dd2e3163 --- /dev/null +++ b/examples/jdbc-v2-json-processors/README.md @@ -0,0 +1,59 @@ +# JDBC V2 JSON Processors Example + +## Overview + +This standalone example shows how to configure `jdbc-v2` to read +`FORMAT JSONEachRow` results with either supported JSON processor: + +- `JACKSON` +- `GSON` + +## Requirements + +- JDK 17 or newer +- A running ClickHouse server reachable from the machine running the example +- A locally installed `jdbc-v2` snapshot from this repository + +## How to Run + +From this directory: + +```shell +gradle run +``` + +The example runs both processors by default. To run only one of them: + +```shell +gradle run -DjsonProcessor=JACKSON +``` + +Connection properties can be supplied as system properties: + +- `-DchUrl` - JDBC URL (default: `jdbc:clickhouse://localhost:8123/default`) +- `-DchUser` - ClickHouse user name (default: `default`) +- `-DchPassword` - ClickHouse user password (default: empty) +- `-DjsonProcessor` - One processor to run: `JACKSON` or `GSON` + +Example with custom connection properties: + +```shell +gradle run \ + -DchUrl=jdbc:clickhouse://localhost:8123/default \ + -DchUser=default \ + -DchPassword= \ + -DjsonProcessor=GSON +``` + +## Executable Example + +`com.clickhouse.examples.jdbc_v2.json_processors.JdbcV2JsonProcessorsExample` + +- Creates a JDBC connection with the `json_processor` connection property set to + `JACKSON` or `GSON`. +- Executes a simple `SELECT ... FORMAT JSONEachRow` query. +- Reads rows back through `ResultSet` and logs the parsed values. + +The build keeps both `jackson-databind` and `gson` on the classpath so the +example can switch between processors at runtime. Production applications only +need to keep the processor they actually use. diff --git a/examples/jdbc-v2-json-processors/build.gradle.kts b/examples/jdbc-v2-json-processors/build.gradle.kts new file mode 100644 index 000000000..65b501e3e --- /dev/null +++ b/examples/jdbc-v2-json-processors/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + application +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation(libs.jdbcV2) + + // Keep both processors on the classpath so the example can switch between them. + implementation(libs.jacksonDatabind) + implementation(libs.gson) + + implementation(libs.slf4jApi) + runtimeOnly(libs.slf4jSimple) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +application { + mainClass = "com.clickhouse.examples.jdbc_v2.json_processors.JdbcV2JsonProcessorsExample" +} + +tasks.named("run") { + listOf("chUrl", "chUser", "chPassword", "jsonProcessor").forEach { key -> + System.getProperty(key)?.let { value -> + systemProperty(key, value) + } + } +} diff --git a/examples/jdbc-v2-json-processors/gradle.properties b/examples/jdbc-v2-json-processors/gradle.properties new file mode 100644 index 000000000..5ad69748c --- /dev/null +++ b/examples/jdbc-v2-json-processors/gradle.properties @@ -0,0 +1 @@ +org.gradle.configuration-cache=true diff --git a/examples/jdbc-v2-json-processors/gradle/libs.versions.toml b/examples/jdbc-v2-json-processors/gradle/libs.versions.toml new file mode 100644 index 000000000..6ac8a57a6 --- /dev/null +++ b/examples/jdbc-v2-json-processors/gradle/libs.versions.toml @@ -0,0 +1,12 @@ +[versions] +jdbcV2 = "0.9.8-SNAPSHOT" +jackson = "2.18.6" +gson = "2.10.1" +slf4j = "2.0.17" + +[libraries] +jdbcV2 = { module = "com.clickhouse:jdbc-v2", version.ref = "jdbcV2" } +jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +slf4jApi = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4jSimple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } diff --git a/examples/jdbc-v2-json-processors/settings.gradle.kts b/examples/jdbc-v2-json-processors/settings.gradle.kts new file mode 100644 index 000000000..4cb564e86 --- /dev/null +++ b/examples/jdbc-v2-json-processors/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "ch-java-jdbc-v2-json-processors" diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java new file mode 100644 index 000000000..fd7e9b123 --- /dev/null +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -0,0 +1,64 @@ +package com.clickhouse.examples.jdbc_v2.json_processors; + +import com.clickhouse.client.api.ClientConfigProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +public class JdbcV2JsonProcessorsExample { + private static final Logger LOG = LoggerFactory.getLogger(JdbcV2JsonProcessorsExample.class); + private static final List SUPPORTED_PROCESSORS = Arrays.asList("JACKSON", "GSON"); + + public static void main(String[] args) throws Exception { + String url = System.getProperty("chUrl", "jdbc:clickhouse://localhost:8123/default"); + String user = System.getProperty("chUser", "default"); + String password = System.getProperty("chPassword", ""); + + for (String processor : requestedProcessors()) { + runExample(url, user, password, processor); + } + } + + private static void runExample(String url, String user, String password, String processor) throws Exception { + Properties properties = new Properties(); + properties.setProperty("user", user); + properties.setProperty("password", password); + properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), processor); + + LOG.info("Running jdbc-v2 JSONEachRow example with processor {}", processor); + + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery( + "SELECT number + 1 AS id, concat('processor-', toString(number + 1)) AS label " + + "FROM numbers(3) FORMAT JSONEachRow")) { + while (rs.next()) { + LOG.info("[{}] id={}, label={}", processor, rs.getInt("id"), rs.getString("label")); + } + } + } + + private static List requestedProcessors() { + String requested = System.getProperty("jsonProcessor", "").trim(); + if (requested.isEmpty()) { + return SUPPORTED_PROCESSORS; + } + + String normalized = requested.toUpperCase(Locale.ROOT); + if (!SUPPORTED_PROCESSORS.contains(normalized)) { + throw new IllegalArgumentException("Unsupported jsonProcessor '" + requested + + "'. Expected one of: " + SUPPORTED_PROCESSORS); + } + + return Collections.singletonList(normalized); + } +} From c5388f007637a82c1f13433d153ca5e136e8e910 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 24 Apr 2026 17:31:55 -0700 Subject: [PATCH 05/26] made both examples runnable. added wrapper --- examples/client-v2-json-processors/README.md | 24 +- .../gradle/wrapper/gradle-wrapper.properties | 7 + examples/client-v2-json-processors/gradlew | 248 ++++++++++++++++++ .../client-v2-json-processors/gradlew.bat | 93 +++++++ .../ClientV2JsonProcessorsExample.java | 101 ++++--- examples/jdbc-v2-json-processors/README.md | 25 +- .../JdbcV2JsonProcessorsExample.java | 94 ++++--- 7 files changed, 502 insertions(+), 90 deletions(-) create mode 100644 examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/client-v2-json-processors/gradlew create mode 100644 examples/client-v2-json-processors/gradlew.bat diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index eb1c12ace..b2a4eefb3 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -3,7 +3,8 @@ ## Overview This standalone example shows how to configure `client-v2` to read `JSONEachRow` -responses with either supported JSON processor: +responses with both supported JSON processors using one shared table and one +shared dataset: - `JACKSON` - `GSON` @@ -22,19 +23,12 @@ From this directory: gradle run ``` -The example runs both processors by default. To run only one of them: - -```shell -gradle run -DjsonProcessor=GSON -``` - Connection properties can be supplied as system properties: - `-DchEndpoint` - Endpoint to connect to (default: `http://localhost:8123`) - `-DchUser` - ClickHouse user name (default: `default`) - `-DchPassword` - ClickHouse user password (default: empty) - `-DchDatabase` - ClickHouse database name (default: `default`) -- `-DjsonProcessor` - One processor to run: `JACKSON` or `GSON` Example with custom connection properties: @@ -43,19 +37,21 @@ gradle run \ -DchEndpoint=http://localhost:8123 \ -DchUser=default \ -DchPassword= \ - -DchDatabase=default \ - -DjsonProcessor=JACKSON + -DchDatabase=default ``` ## Executable Example `com.clickhouse.examples.client_v2.json_processors.ClientV2JsonProcessorsExample` -- Creates a `client-v2` instance with `json_processor` set to `JACKSON` or - `GSON`. -- Executes a simple `SELECT ... FROM numbers(3)` query in `JSONEachRow` format. +- Runs the following steps in order: + 1. defines table `client_v2_json_processors_example` with primitive columns + and one `payload JSON` column; + 2. loads one fixed dataset into that table; + 3. reads the same rows with `runGsonExample(...)`; + 4. reads the same rows again with `runJacksonExample(...)`. - Reads rows back through `client.newBinaryFormatReader(response)` and logs the - parsed values. + primitive columns together with the parsed JSON object from `payload`. The build keeps both `jackson-databind` and `gson` on the classpath so the example can switch between processors at runtime. Production applications only diff --git a/examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.properties b/examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..c61a118f7 --- /dev/null +++ b/examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/client-v2-json-processors/gradlew b/examples/client-v2-json-processors/gradlew new file mode 100755 index 000000000..739907dfd --- /dev/null +++ b/examples/client-v2-json-processors/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/client-v2-json-processors/gradlew.bat b/examples/client-v2-json-processors/gradlew.bat new file mode 100644 index 000000000..e509b2dd8 --- /dev/null +++ b/examples/client-v2-json-processors/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index ee4dcb188..fcd1a6cc9 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -2,6 +2,7 @@ import com.clickhouse.client.api.Client; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; @@ -9,58 +10,94 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; +import java.util.Map; public class ClientV2JsonProcessorsExample { private static final Logger LOG = LoggerFactory.getLogger(ClientV2JsonProcessorsExample.class); - private static final List SUPPORTED_PROCESSORS = Arrays.asList("JACKSON", "GSON"); + private static final String TABLE_NAME = "client_v2_json_processors_example"; + private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" + + "id UInt32, " + + "name String, " + + "active Bool, " + + "score Float64, " + + "payload JSON" + + ") ENGINE = MergeTree ORDER BY id"; + private static final String INSERT_DATA_SQL = "INSERT INTO " + TABLE_NAME + " (id, name, active, score, payload) VALUES " + + "(1, 'first row', true, 10.5, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":1,\"weight\":10.5}}'), " + + "(2, 'second row', false, 20.25, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":2,\"weight\":20.25}}')"; + private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " + + "FROM " + TABLE_NAME + " ORDER BY id"; public static void main(String[] args) throws Exception { ConnectionConfig config = ConnectionConfig.load(); + defineTableStructure(config); + loadData(config); - for (String processor : requestedProcessors()) { - runExample(config, processor); + runGsonExample(config); + runJacksonExample(config); + } + + private static void defineTableStructure(ConnectionConfig config) throws Exception { + LOG.info("Step 1. Defining table structure: {}", TABLE_NAME); + try (Client client = createClient(config, "GSON")) { + executeStatement(client, "DROP TABLE IF EXISTS " + TABLE_NAME); + executeStatement(client, CREATE_TABLE_SQL); } } - private static void runExample(ConnectionConfig config, String processor) throws Exception { - LOG.info("Running client-v2 JSONEachRow example with processor {}", processor); + private static void loadData(ConnectionConfig config) throws Exception { + LOG.info("Step 2. Loading sample data into {}", TABLE_NAME); + try (Client client = createClient(config, "GSON")) { + executeStatement(client, "TRUNCATE TABLE " + TABLE_NAME); + executeStatement(client, INSERT_DATA_SQL); + } + } - try (Client client = new Client.Builder() + private static void runJacksonExample(ConnectionConfig config) throws Exception { + LOG.info("Step 4. Running client-v2 example with Jackson"); + try (Client client = createClient(config, "JACKSON")) { + readRows(client, "JACKSON"); + } + } + + private static void runGsonExample(ConnectionConfig config) throws Exception { + LOG.info("Step 3. Running client-v2 example with Gson"); + try (Client client = createClient(config, "GSON")) { + readRows(client, "GSON"); + } + } + + private static void readRows(Client client, String processor) throws Exception { + try (QueryResponse response = client.query(SELECT_DATA_SQL, new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)).get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + while (reader.next() != null) { + Map payload = reader.readValue("payload"); + LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}", + processor, + reader.getInteger("id"), + reader.getString("name"), + reader.getBoolean("active"), + reader.getDouble("score"), + payload); + } + } + } + + private static Client createClient(ConnectionConfig config, String processor) { + return new Client.Builder() .addEndpoint(config.endpoint) .setUsername(config.user) .setPassword(config.password) .setDefaultDatabase(config.database) - // `json_processor` selects the parser used by `newBinaryFormatReader(...)`. + .serverSetting("allow_experimental_json_type", "1") .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), processor) .build(); - QueryResponse response = client.query( - "SELECT number + 1 AS id, concat('processor-', toString(number + 1)) AS label FROM numbers(3)", - new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); - while (reader.hasNext()) { - reader.next(); - LOG.info("[{}] id={}, label={}", processor, reader.getInteger("id"), reader.getString("label")); - } - } } - private static List requestedProcessors() { - String requested = System.getProperty("jsonProcessor", "").trim(); - if (requested.isEmpty()) { - return SUPPORTED_PROCESSORS; + private static void executeStatement(Client client, String sql) throws Exception { + try (CommandResponse ignored = client.execute(sql).get()) { + LOG.debug("Executed SQL: {}", sql); } - - String normalized = requested.toUpperCase(Locale.ROOT); - if (!SUPPORTED_PROCESSORS.contains(normalized)) { - throw new IllegalArgumentException("Unsupported jsonProcessor '" + requested - + "'. Expected one of: " + SUPPORTED_PROCESSORS); - } - - return Collections.singletonList(normalized); } private static final class ConnectionConfig { diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md index 3dd2e3163..c22192179 100644 --- a/examples/jdbc-v2-json-processors/README.md +++ b/examples/jdbc-v2-json-processors/README.md @@ -3,7 +3,8 @@ ## Overview This standalone example shows how to configure `jdbc-v2` to read -`FORMAT JSONEachRow` results with either supported JSON processor: +`FORMAT JSONEachRow` results with both supported JSON processors using one +shared table and one shared dataset: - `JACKSON` - `GSON` @@ -22,18 +23,11 @@ From this directory: gradle run ``` -The example runs both processors by default. To run only one of them: - -```shell -gradle run -DjsonProcessor=JACKSON -``` - Connection properties can be supplied as system properties: - `-DchUrl` - JDBC URL (default: `jdbc:clickhouse://localhost:8123/default`) - `-DchUser` - ClickHouse user name (default: `default`) - `-DchPassword` - ClickHouse user password (default: empty) -- `-DjsonProcessor` - One processor to run: `JACKSON` or `GSON` Example with custom connection properties: @@ -41,18 +35,21 @@ Example with custom connection properties: gradle run \ -DchUrl=jdbc:clickhouse://localhost:8123/default \ -DchUser=default \ - -DchPassword= \ - -DjsonProcessor=GSON + -DchPassword= ``` ## Executable Example `com.clickhouse.examples.jdbc_v2.json_processors.JdbcV2JsonProcessorsExample` -- Creates a JDBC connection with the `json_processor` connection property set to - `JACKSON` or `GSON`. -- Executes a simple `SELECT ... FORMAT JSONEachRow` query. -- Reads rows back through `ResultSet` and logs the parsed values. +- Runs the following steps in order: + 1. defines table `jdbc_v2_json_processors_example` with primitive columns and + one `payload JSON` column; + 2. loads one fixed dataset into that table; + 3. reads the same rows with `runGsonExample(...)`; + 4. reads the same rows again with `runJacksonExample(...)`. +- Reads rows back through `ResultSet` and logs the primitive columns together + with the parsed JSON object from `payload`. The build keeps both `jackson-databind` and `gson` on the classpath so the example can switch between processors at runtime. Production applications only diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index fd7e9b123..32302dfa5 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -8,57 +8,91 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; import java.util.Properties; public class JdbcV2JsonProcessorsExample { private static final Logger LOG = LoggerFactory.getLogger(JdbcV2JsonProcessorsExample.class); - private static final List SUPPORTED_PROCESSORS = Arrays.asList("JACKSON", "GSON"); + private static final String TABLE_NAME = "jdbc_v2_json_processors_example"; + private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" + + "id UInt32, " + + "name String, " + + "active Bool, " + + "score Float64, " + + "payload JSON" + + ") ENGINE = MergeTree ORDER BY id"; + private static final String INSERT_DATA_SQL = "INSERT INTO " + TABLE_NAME + " (id, name, active, score, payload) VALUES " + + "(1, 'first row', true, 10.5, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":1,\"weight\":10.5}}'), " + + "(2, 'second row', false, 20.25, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":2,\"weight\":20.25}}')"; + private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " + + "FROM " + TABLE_NAME + " ORDER BY id FORMAT JSONEachRow"; public static void main(String[] args) throws Exception { String url = System.getProperty("chUrl", "jdbc:clickhouse://localhost:8123/default"); String user = System.getProperty("chUser", "default"); String password = System.getProperty("chPassword", ""); + Properties setupProperties = baseProperties(user, password); - for (String processor : requestedProcessors()) { - runExample(url, user, password, processor); + defineTableStructure(url, setupProperties); + loadData(url, setupProperties); + + runGsonExample(url, user, password); + runJacksonExample(url, user, password); + } + + private static void defineTableStructure(String url, Properties properties) throws Exception { + LOG.info("Step 1. Defining table structure: {}", TABLE_NAME); + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement()) { + statement.execute("DROP TABLE IF EXISTS " + TABLE_NAME); + statement.execute(CREATE_TABLE_SQL); } } - private static void runExample(String url, String user, String password, String processor) throws Exception { - Properties properties = new Properties(); - properties.setProperty("user", user); - properties.setProperty("password", password); - properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), processor); + private static void loadData(String url, Properties properties) throws Exception { + LOG.info("Step 2. Loading sample data into {}", TABLE_NAME); + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement()) { + statement.execute("TRUNCATE TABLE " + TABLE_NAME); + statement.executeUpdate(INSERT_DATA_SQL); + } + } - LOG.info("Running jdbc-v2 JSONEachRow example with processor {}", processor); + private static void runJacksonExample(String url, String user, String password) throws Exception { + Properties properties = baseProperties(user, password); + properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); + LOG.info("Step 4. Running jdbc-v2 example with Jackson"); + readRows(url, properties, "JACKSON"); + } + + private static void runGsonExample(String url, String user, String password) throws Exception { + Properties properties = baseProperties(user, password); + properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "GSON"); + LOG.info("Step 3. Running jdbc-v2 example with Gson"); + readRows(url, properties, "GSON"); + } + private static void readRows(String url, Properties properties, String processor) throws Exception { try (Connection connection = DriverManager.getConnection(url, properties); Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery( - "SELECT number + 1 AS id, concat('processor-', toString(number + 1)) AS label " - + "FROM numbers(3) FORMAT JSONEachRow")) { + ResultSet rs = statement.executeQuery(SELECT_DATA_SQL)) { while (rs.next()) { - LOG.info("[{}] id={}, label={}", processor, rs.getInt("id"), rs.getString("label")); + Object payload = rs.getObject("payload"); + LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}", + processor, + rs.getInt("id"), + rs.getString("name"), + rs.getBoolean("active"), + rs.getDouble("score"), + payload); } } } - private static List requestedProcessors() { - String requested = System.getProperty("jsonProcessor", "").trim(); - if (requested.isEmpty()) { - return SUPPORTED_PROCESSORS; - } - - String normalized = requested.toUpperCase(Locale.ROOT); - if (!SUPPORTED_PROCESSORS.contains(normalized)) { - throw new IllegalArgumentException("Unsupported jsonProcessor '" + requested - + "'. Expected one of: " + SUPPORTED_PROCESSORS); - } - - return Collections.singletonList(normalized); + private static Properties baseProperties(String user, String password) { + Properties properties = new Properties(); + properties.setProperty("user", user); + properties.setProperty("password", password); + properties.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); + return properties; } } From 9135c88c34c3ad54767cafdb90ed6321b8f04a7f Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Sun, 26 Apr 2026 06:23:31 +0900 Subject: [PATCH 06/26] Made examples workable and simple --- examples/client-v2-json-processors/README.md | 2 +- .../ClientV2JsonProcessorsExample.java | 128 ++++++++- examples/jdbc-v2-json-processors/README.md | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 7 + examples/jdbc-v2-json-processors/gradlew | 248 ++++++++++++++++++ examples/jdbc-v2-json-processors/gradlew.bat | 93 +++++++ .../JdbcV2JsonProcessorsExample.java | 133 +++++++++- 7 files changed, 600 insertions(+), 13 deletions(-) create mode 100644 examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/jdbc-v2-json-processors/gradlew create mode 100644 examples/jdbc-v2-json-processors/gradlew.bat diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index b2a4eefb3..f8cd7dbe5 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -47,7 +47,7 @@ gradle run \ - Runs the following steps in order: 1. defines table `client_v2_json_processors_example` with primitive columns and one `payload JSON` column; - 2. loads one fixed dataset into that table; + 2. loads sample rows from `src/main/resources/sample_data.csv` into that table; 3. reads the same rows with `runGsonExample(...)`; 4. reads the same rows again with `runJacksonExample(...)`. - Reads rows back through `client.newBinaryFormatReader(response)` and logs the diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index fcd1a6cc9..2f91afa38 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -10,11 +10,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class ClientV2JsonProcessorsExample { private static final Logger LOG = LoggerFactory.getLogger(ClientV2JsonProcessorsExample.class); private static final String TABLE_NAME = "client_v2_json_processors_example"; + private static final String SAMPLE_DATA_RESOURCE = "/sample_data.csv"; private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" + "id UInt32, " + "name String, " @@ -22,9 +30,6 @@ public class ClientV2JsonProcessorsExample { + "score Float64, " + "payload JSON" + ") ENGINE = MergeTree ORDER BY id"; - private static final String INSERT_DATA_SQL = "INSERT INTO " + TABLE_NAME + " (id, name, active, score, payload) VALUES " - + "(1, 'first row', true, 10.5, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":1,\"weight\":10.5}}'), " - + "(2, 'second row', false, 20.25, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":2,\"weight\":20.25}}')"; private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " + "FROM " + TABLE_NAME + " ORDER BY id"; @@ -46,10 +51,11 @@ private static void defineTableStructure(ConnectionConfig config) throws Excepti } private static void loadData(ConnectionConfig config) throws Exception { - LOG.info("Step 2. Loading sample data into {}", TABLE_NAME); + List rows = readSampleRows(); + LOG.info("Step 2. Loading {} sample rows from {} into {}", rows.size(), SAMPLE_DATA_RESOURCE, TABLE_NAME); try (Client client = createClient(config, "GSON")) { executeStatement(client, "TRUNCATE TABLE " + TABLE_NAME); - executeStatement(client, INSERT_DATA_SQL); + executeStatement(client, buildInsertSql(rows)); } } @@ -72,12 +78,13 @@ private static void readRows(Client client, String processor) throws Exception { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); while (reader.next() != null) { Map payload = reader.readValue("payload"); - LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}", + LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}({})", processor, reader.getInteger("id"), reader.getString("name"), reader.getBoolean("active"), reader.getDouble("score"), + payload.getClass().getName(), payload); } } @@ -100,6 +107,115 @@ private static void executeStatement(Client client, String sql) throws Exception } } + private static List readSampleRows() throws IOException { + InputStream stream = ClientV2JsonProcessorsExample.class.getResourceAsStream(SAMPLE_DATA_RESOURCE); + if (stream == null) { + throw new IOException("Resource not found: " + SAMPLE_DATA_RESOURCE); + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String header = reader.readLine(); + if (header == null) { + throw new IOException("CSV resource is empty: " + SAMPLE_DATA_RESOURCE); + } + + List rows = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().isEmpty()) { + continue; + } + + List values = parseCsvLine(line); + if (values.size() != 5) { + throw new IOException("Expected 5 columns in sample CSV but found " + values.size() + ": " + line); + } + + rows.add(new SampleRow( + Integer.parseInt(values.get(0)), + values.get(1), + Boolean.parseBoolean(values.get(2)), + Double.parseDouble(values.get(3)), + values.get(4))); + } + + return rows; + } + } + + private static List parseCsvLine(String line) { + List values = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + + for (int i = 0; i < line.length(); i++) { + char ch = line.charAt(i); + if (ch == '"') { + if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { + current.append('"'); + i++; + } else { + inQuotes = !inQuotes; + } + } else if (ch == ',' && !inQuotes) { + values.add(current.toString()); + current.setLength(0); + } else { + current.append(ch); + } + } + + values.add(current.toString()); + return values; + } + + private static String buildInsertSql(List rows) { + if (rows.isEmpty()) { + throw new IllegalArgumentException("Sample CSV does not contain any rows"); + } + + StringBuilder sql = new StringBuilder("INSERT INTO ") + .append(TABLE_NAME) + .append(" (id, name, active, score, payload) VALUES "); + + for (SampleRow row : rows) { + sql.append('(') + .append(row.id) + .append(", ") + .append(quoteSqlString(row.name)) + .append(", ") + .append(row.active) + .append(", ") + .append(row.score) + .append(", ") + .append(quoteSqlString(row.payload)) + .append("), "); + } + + sql.setLength(sql.length() - 2); + return sql.toString(); + } + + private static String quoteSqlString(String value) { + return "'" + value.replace("'", "''") + "'"; + } + + private static final class SampleRow { + private final int id; + private final String name; + private final boolean active; + private final double score; + private final String payload; + + private SampleRow(int id, String name, boolean active, double score, String payload) { + this.id = id; + this.name = name; + this.active = active; + this.score = score; + this.payload = payload; + } + } + private static final class ConnectionConfig { private final String endpoint; private final String user; diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md index c22192179..c22f2e5ad 100644 --- a/examples/jdbc-v2-json-processors/README.md +++ b/examples/jdbc-v2-json-processors/README.md @@ -45,7 +45,7 @@ gradle run \ - Runs the following steps in order: 1. defines table `jdbc_v2_json_processors_example` with primitive columns and one `payload JSON` column; - 2. loads one fixed dataset into that table; + 2. loads sample rows from `src/main/resources/sample_data.csv` into that table; 3. reads the same rows with `runGsonExample(...)`; 4. reads the same rows again with `runJacksonExample(...)`. - Reads rows back through `ResultSet` and logs the primitive columns together diff --git a/examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.properties b/examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..c61a118f7 --- /dev/null +++ b/examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/jdbc-v2-json-processors/gradlew b/examples/jdbc-v2-json-processors/gradlew new file mode 100755 index 000000000..739907dfd --- /dev/null +++ b/examples/jdbc-v2-json-processors/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/jdbc-v2-json-processors/gradlew.bat b/examples/jdbc-v2-json-processors/gradlew.bat new file mode 100644 index 000000000..e509b2dd8 --- /dev/null +++ b/examples/jdbc-v2-json-processors/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index 32302dfa5..58a465a1b 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -1,18 +1,27 @@ package com.clickhouse.examples.jdbc_v2.json_processors; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; public class JdbcV2JsonProcessorsExample { private static final Logger LOG = LoggerFactory.getLogger(JdbcV2JsonProcessorsExample.class); private static final String TABLE_NAME = "jdbc_v2_json_processors_example"; + private static final String SAMPLE_DATA_RESOURCE = "/sample_data.csv"; private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" + "id UInt32, " + "name String, " @@ -20,9 +29,6 @@ public class JdbcV2JsonProcessorsExample { + "score Float64, " + "payload JSON" + ") ENGINE = MergeTree ORDER BY id"; - private static final String INSERT_DATA_SQL = "INSERT INTO " + TABLE_NAME + " (id, name, active, score, payload) VALUES " - + "(1, 'first row', true, 10.5, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":1,\"weight\":10.5}}'), " - + "(2, 'second row', false, 20.25, '{\"source\":\"examples\",\"tags\":[\"demo\",\"shared\"],\"metrics\":{\"rank\":2,\"weight\":20.25}}')"; private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " + "FROM " + TABLE_NAME + " ORDER BY id FORMAT JSONEachRow"; @@ -32,6 +38,7 @@ public static void main(String[] args) throws Exception { String password = System.getProperty("chPassword", ""); Properties setupProperties = baseProperties(user, password); + registerDriver(); defineTableStructure(url, setupProperties); loadData(url, setupProperties); @@ -49,11 +56,12 @@ private static void defineTableStructure(String url, Properties properties) thro } private static void loadData(String url, Properties properties) throws Exception { - LOG.info("Step 2. Loading sample data into {}", TABLE_NAME); + List rows = readSampleRows(); + LOG.info("Step 2. Loading {} sample rows from {} into {}", rows.size(), SAMPLE_DATA_RESOURCE, TABLE_NAME); try (Connection connection = DriverManager.getConnection(url, properties); Statement statement = connection.createStatement()) { statement.execute("TRUNCATE TABLE " + TABLE_NAME); - statement.executeUpdate(INSERT_DATA_SQL); + statement.executeUpdate(buildInsertSql(rows)); } } @@ -95,4 +103,119 @@ private static Properties baseProperties(String user, String password) { properties.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); return properties; } + + private static void registerDriver() { + // `jdbc-v2` does not self-register from its static initializer, so standalone + // examples should register it explicitly before calling DriverManager. + Driver.load(); + } + + private static List readSampleRows() throws IOException { + InputStream stream = JdbcV2JsonProcessorsExample.class.getResourceAsStream(SAMPLE_DATA_RESOURCE); + if (stream == null) { + throw new IOException("Resource not found: " + SAMPLE_DATA_RESOURCE); + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String header = reader.readLine(); + if (header == null) { + throw new IOException("CSV resource is empty: " + SAMPLE_DATA_RESOURCE); + } + + List rows = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().isEmpty()) { + continue; + } + + List values = parseCsvLine(line); + if (values.size() != 5) { + throw new IOException("Expected 5 columns in sample CSV but found " + values.size() + ": " + line); + } + + rows.add(new SampleRow( + Integer.parseInt(values.get(0)), + values.get(1), + Boolean.parseBoolean(values.get(2)), + Double.parseDouble(values.get(3)), + values.get(4))); + } + + return rows; + } + } + + private static List parseCsvLine(String line) { + List values = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + + for (int i = 0; i < line.length(); i++) { + char ch = line.charAt(i); + if (ch == '"') { + if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { + current.append('"'); + i++; + } else { + inQuotes = !inQuotes; + } + } else if (ch == ',' && !inQuotes) { + values.add(current.toString()); + current.setLength(0); + } else { + current.append(ch); + } + } + + values.add(current.toString()); + return values; + } + + private static String buildInsertSql(List rows) { + if (rows.isEmpty()) { + throw new IllegalArgumentException("Sample CSV does not contain any rows"); + } + + StringBuilder sql = new StringBuilder("INSERT INTO ") + .append(TABLE_NAME) + .append(" (id, name, active, score, payload) VALUES "); + + for (SampleRow row : rows) { + sql.append('(') + .append(row.id) + .append(", ") + .append(quoteSqlString(row.name)) + .append(", ") + .append(row.active) + .append(", ") + .append(row.score) + .append(", ") + .append(quoteSqlString(row.payload)) + .append("), "); + } + + sql.setLength(sql.length() - 2); + return sql.toString(); + } + + private static String quoteSqlString(String value) { + return "'" + value.replace("'", "''") + "'"; + } + + private static final class SampleRow { + private final int id; + private final String name; + private final boolean active; + private final double score; + private final String payload; + + private SampleRow(int id, String name, boolean active, double score, String payload) { + this.id = id; + this.name = name; + this.active = active; + this.score = score; + this.payload = payload; + } + } } From f513bb59afd83a1139c5bec765be009d9f020d8e Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Sun, 26 Apr 2026 06:42:54 +0900 Subject: [PATCH 07/26] Added gradle wrapper jars --- .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.jar diff --git a/examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.jar b/examples/client-v2-json-processors/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 diff --git a/examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.jar b/examples/jdbc-v2-json-processors/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 From 38297368c671c0a118d0234a344ad6b8b52b9a70 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Sun, 26 Apr 2026 18:30:12 +0900 Subject: [PATCH 08/26] Added required server settings --- .../com/clickhouse/client/api/Client.java | 25 +++++++++++++++ .../AbstractJSONEachRowFormatReaderTests.java | 32 ++++++++++--------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 4e96628a8..9cc857ee6 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -1666,6 +1666,7 @@ public CompletableFuture query(String sqlQuery, Map buildRequestSettings(Map opSettings) return requestSettings; } + /** + * Applies format-specific server-side settings to the already merged request settings. + * Must be called after {@link #buildRequestSettings(Map)} and after the request format has been resolved + * (either provided by the caller or defaulted), so that the inspected format reflects the final value. + * + *

For {@link ClickHouseFormat#JSONEachRow} the JSON output flags below are forced to {@code 0} so that the + * stream contains plain JSON numbers (and not quoted strings or non-standard tokens), which is what + * {@link com.clickhouse.client.api.data_formats.JSONEachRowFormatReader} expects:

+ *
    + *
  • {@code output_format_json_quote_64bit_integers}
  • + *
  • {@code output_format_json_quote_64bit_floats}
  • + *
  • {@code output_format_json_quote_denormals}
  • + *
  • {@code output_format_json_quote_decimals}
  • + *
+ */ + private static void applyFormatSpecificSettings(QuerySettings requestSettings) { + if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow) { + requestSettings.serverSetting("output_format_json_quote_64bit_integers", "0"); + requestSettings.serverSetting("output_format_json_quote_64bit_floats", "0"); + requestSettings.serverSetting("output_format_json_quote_denormals", "0"); + requestSettings.serverSetting("output_format_json_quote_decimals", "0"); + } + } + private Duration durationSince(long sinceNanos) { return Duration.ofNanos(System.nanoTime() - sinceNanos); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 41c1398ae..1a760c334 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -42,13 +42,17 @@ public void tearDown() { } } + private QuerySettings newJsonEachRowSettings() { + return new QuerySettings() + .setFormat(ClickHouseFormat.JSONEachRow); + } + @Test(groups = {"integration"}) public void testBasicParsing() throws Exception { String sql = "SELECT 1 as id, 'test' as name, true as active " + - "UNION ALL SELECT 2, 'clickhouse', false " + - "FORMAT JSONEachRow"; - - try (QueryResponse response = client.query(sql).get()) { + "UNION ALL SELECT 2, 'clickhouse', false"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); // First row @@ -75,10 +79,9 @@ public void testBasicParsing() throws Exception { @Test(groups = {"integration"}) public void testSchemaInference() throws Exception { String sql = "SELECT toInt64(42) as col_int, toFloat64(3.14) as col_float, " + - "true as col_bool, 'val' as col_str " + - "FORMAT JSONEachRow"; - - try (QueryResponse response = client.query(sql).get()) { + "true as col_bool, 'val' as col_str"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); Assert.assertNotNull(reader.getSchema()); @@ -95,10 +98,9 @@ public void testSchemaInference() throws Exception { public void testDataTypes() throws Exception { String sql = "SELECT toInt8(120) as b, toInt16(30000) as s, toInt32(1000000) as i, " + "toInt64(10000000000) as l, toFloat32(1.23) as f, toFloat64(1.23456789) as d, " + - "true as bool, 'hello' as str " + - "FORMAT JSONEachRow"; - - try (QueryResponse response = client.query(sql).get()) { + "true as bool, 'hello' as str"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); reader.next(); @@ -115,9 +117,9 @@ public void testDataTypes() throws Exception { @Test(groups = {"integration"}) public void testEmptyData() throws Exception { - String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1 FORMAT JSONEachRow"; - - try (QueryResponse response = client.query(sql).get()) { + String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); Assert.assertFalse(reader.hasNext()); From e76496039947d2b3510244d814a4cd90c5d8f680 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Mon, 27 Apr 2026 09:21:58 +0900 Subject: [PATCH 09/26] initial docs --- docs/client-v2-json-support.md | 425 +++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 docs/client-v2-json-support.md diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md new file mode 100644 index 000000000..00f043756 --- /dev/null +++ b/docs/client-v2-json-support.md @@ -0,0 +1,425 @@ +# JSONEachRow Support in `client-v2` and `jdbc-v2` + +This document specifies the `JSONEachRow` output-format support introduced in +`client-v2` and exposed through `jdbc-v2`. It defines the public API, +configuration properties, runtime dependencies, type mapping, and current +limitations. + +## Motivation + +ClickHouse provides several JSON-oriented column types (`JSON`, `Variant`, +`Dynamic`) and structured types (`Array`, `Tuple`, `Map`). When such values +are returned through binary formats they are commonly materialized as +serialized strings, which requires every caller to embed and configure its own +JSON parser and complicates the propagation of nested objects through JDBC. + +`JSONEachRow` is the row-oriented JSON output format of ClickHouse: each row +is emitted as a self-contained JSON object separated by line breaks. With the +appropriate server settings, numeric values are emitted as JSON numbers and +nested objects are preserved without additional encoding. Supporting this +format directly in the Java clients enables: + +- materializing columns of type `JSON` as `Map` instances; +- preserving nested objects, arrays, and tuples without an additional parsing + step; +- exposing structured JSON payloads through the standard JDBC `ResultSet` + contract. + +Combining `JSONEachRow` output with a pluggable Jackson or Gson parser +provides additional advantages beyond what the format alone delivers: + +- **Streaming row parsing.** Jackson's `JsonParser` and Gson's `JsonReader` + consume the response stream incrementally. `JSONEachRowFormatReader` + materializes one row at a time, so peak memory consumption is bounded by + the size of the current row rather than by the size of the result set. +- **Reuse of an existing JSON dependency on the classpath.** Applications + that already depend on Jackson or Gson for unrelated purposes can select + the matching processor through `JSON_PROCESSOR` and avoid contributing a + second JSON library to the runtime classpath. Only the library JARs are + shared; the reader uses its own default `ObjectMapper` or `Gson` instance + and does not pick up the application's configured modules, `TypeAdapter`s, + or other customizations. +- **Choice between processors.** Jackson and Gson are selected independently + and can be swapped at deployment time. Applications may pick the processor + that best matches their existing classpath and operational constraints, + without changing application code that consumes the reader or the JDBC + `ResultSet`. + +## Summary of changes + +`client-v2`: + +- Adds `com.clickhouse.client.api.data_formats.JSONEachRowFormatReader`, + which implements `ClickHouseBinaryFormatReader` over a streaming JSON + parser. +- Extends `Client.newBinaryFormatReader(...)` to construct the reader when + `QuerySettings.getFormat() == ClickHouseFormat.JSONEachRow`. +- Introduces an internal JSON parser SPI under + `com.clickhouse.client.api.data_formats.internal`, consisting of + `JsonParser`, `JsonParserFactory`, `JacksonJsonParser`, and + `GsonJsonParser`. +- Adds the client option `ClientConfigProperties.JSON_PROCESSOR` + (key `json_processor`), with default value `JACKSON` and accepted values + `JACKSON` and `GSON`. +- Forces a fixed set of server settings for `JSONEachRow` requests so that + the response stream contains plain JSON numbers (see + [Forced server settings](#forced-server-settings-for-jsoneachrow)). +- Declares Jackson and Gson as `provided` Maven dependencies, so that + applications must include the chosen processor on their own classpath. + +`jdbc-v2`: + +- Modifies `StatementImpl.executeQuery(...)` to accept `JSONEachRow` as a + valid output format. All other text formats remain unsupported. +- Forwards the `json_processor` option to the underlying `client-v2` + configuration; it is configured through `Properties` or the JDBC URL. +- Declares Jackson and Gson as `provided` dependencies, consistent with + `client-v2`. + +Two runnable examples are included in the repository: +`examples/client-v2-json-processors` and `examples/jdbc-v2-json-processors`. + +## Public API + +### `JSONEachRowFormatReader` + +```java +package com.clickhouse.client.api.data_formats; + +public class JSONEachRowFormatReader implements ClickHouseBinaryFormatReader { ... } +``` + +The reader is normally instantiated by `Client.newBinaryFormatReader(...)`. +The class is public so that callers can construct it from any +`InputStream`-backed `JsonParser`. + +`JSONEachRow` is a text format, but the reader currently implements +`ClickHouseBinaryFormatReader` so that existing call sites — including +`Client.newBinaryFormatReader(...)` — accept JSON output without changes. +The interface name is therefore not a precise fit for this format; a +dedicated reader interface for non-binary formats is intended to replace +this arrangement in a future release. Callers should treat the class itself +as the stable API and avoid relying on the "binary" label of the interface. + +### `JsonParser` SPI + +```java +package com.clickhouse.client.api.data_formats.internal; + +public interface JsonParser extends AutoCloseable { + Map nextRow() throws Exception; +} +``` + +Two implementations are provided: + +- `JacksonJsonParser` uses `com.fasterxml.jackson.core` and + `com.fasterxml.jackson.databind` to stream JSON objects. +- `GsonJsonParser` uses `com.google.gson` with a lenient `JsonReader`, + which accepts a sequence of top-level JSON objects separated by whitespace, + as produced by `JSONEachRow`. + +`JsonParserFactory.createParser(String type, InputStream)` selects an +implementation based on the value of the `JSON_PROCESSOR` option. +Implementations are loaded reflectively. When the requested implementation is +unavailable, the factory throws a `RuntimeException` whose message identifies +the missing dependency. + +The shipped implementations construct their own `ObjectMapper` and `Gson` +instances with default settings. Injecting a pre-configured parser, custom +Jackson modules, or Gson `TypeAdapter`s is not part of the public API in the +current release: neither parser exposes a constructor that accepts a +configured library instance, the `JsonParser` SPI itself resides in an +`internal` package and is not a stable extension point, and +`JsonParserFactory` does not accept caller-supplied implementation classes. +Customization of JSON binding should therefore be performed on the caller +side, after the row has been materialized as `Map`. + +### `JSON_PROCESSOR` configuration property + +Defined in `ClientConfigProperties`: + +| Property key | Default | Accepted values | +| ----------------- | --------- | ----------------- | +| `json_processor` | `JACKSON` | `JACKSON`, `GSON` | + +The same property is used by the `client-v2` builder +(`ClientConfigProperties.JSON_PROCESSOR.getKey()`) and by the JDBC driver, +where it is supplied through `Properties` or the JDBC URL query string. + +## Runtime dependencies + +`client-v2` and `jdbc-v2` declare the JSON libraries with `provided` scope, +so that they are not contributed to the runtime classpath of applications +that do not require them. Applications must add exactly one of the following +to their runtime classpath: + +- Jackson — `com.fasterxml.jackson.core:jackson-databind`, + `com.fasterxml.jackson.core:jackson-core`, + `com.fasterxml.jackson.core:jackson-annotations` + (required when `JSON_PROCESSOR=JACKSON`, the default); or +- Gson — `com.google.code.gson:gson` + (required when `JSON_PROCESSOR=GSON`). + +The repository builds against Jackson `2.17.2` and Gson `2.10.1`. The parser +implementations rely only on the streaming token API and a single +`Map` materialization call, so other recent versions of +either library are expected to be compatible. + +When the configured processor is not present on the classpath at the time a +`JSONEachRow` reader is constructed, `JsonParserFactory` throws a +`RuntimeException` with the following message: + +```text +JSON processor class not found: com.clickhouse.client.api.data_formats.internal.JacksonJsonParser. +Make sure you have the required library (Jackson or Gson) on your classpath. +``` + +## Usage in `client-v2` + +```java +Client client = new Client.Builder() + .addEndpoint("http://localhost:8123") + .setUsername("default") + .setPassword("") + .setDefaultDatabase("default") + .serverSetting("allow_experimental_json_type", "1") + .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON") // or "GSON" + .build(); + +QuerySettings settings = new QuerySettings() + .setFormat(ClickHouseFormat.JSONEachRow); + +try (QueryResponse response = client.query( + "SELECT id, name, active, score, payload FROM events ORDER BY id", + settings).get()) { + + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + while (reader.next() != null) { + int id = reader.getInteger("id"); + String name = reader.getString("name"); + boolean active = reader.getBoolean("active"); + double score = reader.getDouble("score"); + Map payload = reader.readValue("payload"); // JSON column + // ... + } +} +``` + +Notes: + +- The reader is constructed only when the request format is `JSONEachRow`. + The default request format remains `RowBinaryWithNamesAndTypes`, and + callers that do not explicitly opt in are not affected. +- `client.newBinaryFormatReader(response)` returns a reader matching + `response.getFormat()`; the same call site applies to both binary and JSON + output. +- `Map` is the canonical materialization for JSON columns + and for the row itself, as produced by the selected library. JSON arrays + are returned as `List`; nested JSON objects are returned as nested + `Map` instances. The exact Java types of leaf values are + whatever Jackson or Gson chose during parsing. + +## Usage in `jdbc-v2` + +The output format is selected by appending `FORMAT JSONEachRow` to the SQL +statement. The driver does not rewrite the SQL and does not apply a default +format on the caller's behalf. + +```java +Properties props = new Properties(); +props.setProperty("user", "default"); +props.setProperty("password", ""); +props.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); // or "GSON" +// The JSON column type is experimental on the server side. +props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); + +try (Connection conn = DriverManager.getConnection( + "jdbc:clickhouse://localhost:8123/default", props); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery( + "SELECT id, name, active, score, payload " + + "FROM events ORDER BY id FORMAT JSONEachRow")) { + + while (rs.next()) { + int id = rs.getInt("id"); + String name = rs.getString("name"); + boolean active = rs.getBoolean("active"); + double score = rs.getDouble("score"); + Object payload = rs.getObject("payload"); // Map / List / scalar + } +} +``` + +Behavior: + +- When `FORMAT JSONEachRow` is not specified, `jdbc-v2` continues to use the + binary default. `StatementImpl` accepts only `RowBinaryWithNamesAndTypes` + and `JSONEachRow` as output formats; any other text format causes + `SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported + for output format. ...")` to be thrown. +- `ResultSet.getObject(...)` returns the value produced directly by the + selected JSON parser (`Map`, `List`, or scalar), without an additional + string round-trip. +- The JSON processor is selected at the connection level through the + `json_processor` option. It cannot be changed per statement, in line with + the lifecycle of other client options. + +## Forced server settings for `JSONEachRow` + +`Client.applyFormatSpecificSettings(...)` runs after request settings have +been merged and after the request format has been resolved. When the format +is `JSONEachRow`, the following server-side settings are forced for the +request: + +| Setting | Forced value | Rationale | +| ----------------------------------------- | ------------ | -------------------------------------------------------------------------- | +| `output_format_json_quote_64bit_integers` | `0` | Emits `Int64` and `UInt64` as JSON numbers rather than quoted strings. | +| `output_format_json_quote_64bit_floats` | `0` | Emits 64-bit floating-point values as JSON numbers. | +| `output_format_json_quote_denormals` | `0` | Avoids quoting `NaN` and `Inf`, allowing materialization as `Double`. | +| `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. | + +These overrides are scoped to the individual request and apply only when the +request format is `JSONEachRow`. They are required for the typed accessors of +the reader to operate correctly and must not be overridden by callers. + +## Row parsing, schema, and typed accessors + +### Row parsing is delegated to the chosen library + +The reader does not implement its own JSON parser. Each row is materialized +by the configured library: + +- the Jackson backend calls `ObjectMapper.readValue(parser, Map.class)` on + Jackson's streaming `JsonParser`; +- the Gson backend calls `gson.fromJson(reader, TypeToken>)` + on a lenient Gson `JsonReader`. + +The result of each call is a `Map` whose values have the +runtime Java types chosen by the library for the parsed JSON tokens — +typically `Number` (for example `Integer`, `Long`, `Double`, `BigDecimal`), +`Boolean`, `String`, `List` for JSON arrays, nested +`Map` for JSON objects, and `null` for JSON `null`. Numeric +representation, widening rules, handling of large integers, and any other +JSON-to-Java decisions are governed entirely by the library. The reader +neither inspects raw JSON tokens nor overrides the library's parsing +behavior. + +### Minimal schema discovery + +`JSONEachRow` does not include a schema header. To populate a minimal +`TableSchema` for the typed accessors, the reader inspects the **Java +types** of the first row's values, after the library has produced them, and +maps each to a `ClickHouseDataType`: + +| Java type produced by the library | Inferred ClickHouse type | +| ---------------------------------------------------------------------- | ------------------------ | +| `Integer`, `Long`, `BigInteger` | `Int64` | +| `Double`, `Float`, `BigDecimal` whose value has no fractional part within the `long` range | `Int64` | +| Other `Number` subtypes | `Float64` | +| `Boolean` | `Bool` | +| Any other value (`String`, `List`, `Map`, `null`, ...) | `String` | + +Implications: + +- Schema discovery is performed once, on the first row. Empty result sets + produce a schema with no columns. +- Column names are taken verbatim from the JSON keys of the first row, in + iteration order. +- The discovered schema is intended only to support the typed accessors + (`getInteger`, `getString`, and so on). Server-side column metadata such + as precision, nullability, and codec is not reconstructed. +- Whether a JSON number is materialized as `Integer`, `Long`, `Double`, or + `BigDecimal` is a property of the chosen library, not of the reader. + Applications that need a specific numeric representation should select + the processor whose default behavior matches their expectations. + +### Typed accessors + +The typed accessors declared on the read interface are implemented as +follows: + +| Accessor | Behavior | +| --------------------------------------------- | ------------------------------------------------------------- | +| `readValue` / `next` | Returns the row as a `Map`. | +| `getString` | Returns `Object#toString()` of the JSON value, or `null`. | +| `getByte` / `getShort` / `getInteger` / `getLong` / `getFloat` / `getDouble` | Casts through `Number`. | +| `getBoolean` | Accepts `Boolean`, non-zero `Number`, or parses a string. | +| `getBigInteger` / `getBigDecimal` | Routes through `BigDecimal(String)`. | +| `getLocalDate` / `getLocalTime` / `getLocalDateTime` / `getOffsetDateTime` | Uses the corresponding `parse(...)` method on the string value. | +| `getUUID` | Uses `UUID.fromString(...)` on the string value. | +| `getList` | Returns the JSON array as `List`. | +| `getTuple` | Returns the row value cast to `Object[]`. | +| `getEnum8` / `getEnum16` | Delegates to `getByte` / `getShort`. | + +The following accessors are not supported by the current implementation and +throw `UnsupportedOperationException`: + +- `getInstant`, `getZonedDateTime`, `getDuration`, `getTemporalAmount`; +- `getInet4Address`, `getInet6Address`; +- `getGeoPoint`, `getGeoRing`, `getGeoPolygon`, `getGeoMultiPolygon`; +- the typed array accessors `getByteArray`, `getIntArray`, `getLongArray`, + `getFloatArray`, `getDoubleArray`, `getBooleanArray`, `getShortArray`, + `getStringArray`, `getObjectArray`; +- `getClickHouseBitmap`. + +For these types, callers should obtain the parsed value through +`readValue(...)` or `getList(...)` and convert it explicitly. + +## Streaming and lifetime + +- `JacksonJsonParser` and `GsonJsonParser` delegate parsing to the + underlying library and consume one row at a time from the response + `InputStream`. Memory consumption is proportional to the size of the + current row and is independent of the size of the result set. +- `JSONEachRowFormatReader` reads the first row eagerly during construction + in order to inspect the Java types of its values and populate the minimal + `TableSchema` described above. For empty result sets, the reader exposes + an empty `TableSchema`, and `hasNext()` returns `false`. +- `close()` is propagated to the underlying parser, which closes the input + stream it owns. Callers are responsible for closing the originating + `QueryResponse` (or JDBC `ResultSet`). + +## Compatibility considerations + +- The `JSON_PROCESSOR` property is additive. Applications that do not request + `JSONEachRow` and do not set this property are unaffected. +- The default request format is unchanged. The existing binary readers + (`Native`, `RowBinary`, `RowBinaryWithNames`, `RowBinaryWithNamesAndTypes`) + retain their previous behavior. +- Jackson and Gson are now declared with `provided` scope in `client-v2` and + `jdbc-v2`. Applications that previously inherited Jackson transitively from + these modules in `test` scope must declare the chosen processor explicitly + on their runtime classpath. +- The previously undocumented `json_processor` driver property has been + removed from `DriverProperties`. The same value is now configured as a + regular client option; the runtime behavior is unchanged. + +## Examples + +Two runnable Gradle examples are provided under `examples/`: + +- `examples/client-v2-json-processors` exercises the `client-v2` API + directly, switching between `JACKSON` and `GSON` against a shared sample + table that contains primitive columns and one `payload JSON` column. + Entry point: `ClientV2JsonProcessorsExample`. +- `examples/jdbc-v2-json-processors` performs the same flow through the JDBC + driver, with `FORMAT JSONEachRow` appended to the `SELECT` statement and + the same `JACKSON` / `GSON` selection applied through connection + properties. Entry point: `JdbcV2JsonProcessorsExample`. + +Both examples include a sample dataset under +`src/main/resources/sample_data.csv` and require a running ClickHouse server +with `allow_experimental_json_type=1`. + +## Tests + +- `client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java` + defines a parameterized integration test executed for both processors via + the subclasses `JacksonJSONEachRowFormatReaderTests` and + `GsonJSONEachRowFormatReaderTests`. The suite covers basic parsing, schema + inference, primitive type accessors, and empty result sets. +- `jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java` adds the + test methods `testJSONEachRowFormat` and `testJSONEachRowFormatGson`, + which exercise `Statement.executeQuery("... FORMAT JSONEachRow")` through + the JDBC driver against both processors. From 272aa16236e75ef6c73e40b550c16ff98b7ddfef Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Mon, 27 Apr 2026 09:51:08 +0900 Subject: [PATCH 10/26] Created a text reader interface to separate binary and text readers. --- .../com/clickhouse/client/api/Client.java | 30 +- .../ClickHouseBinaryFormatReader.java | 633 +----------------- .../data_formats/ClickHouseFormatReader.java | 629 +++++++++++++++++ .../ClickHouseTextFormatReader.java | 20 + .../data_formats/JSONEachRowFormatReader.java | 2 +- .../AbstractJSONEachRowFormatReaderTests.java | 31 +- docs/client-v2-json-support.md | 76 ++- examples/client-v2-json-processors/README.md | 2 +- .../ClientV2JsonProcessorsExample.java | 4 +- .../com/clickhouse/jdbc/ResultSetImpl.java | 6 +- .../com/clickhouse/jdbc/StatementImpl.java | 8 +- 11 files changed, 784 insertions(+), 657 deletions(-) create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseFormatReader.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 9cc857ee6..5230d5869 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -3,6 +3,7 @@ import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.command.CommandSettings; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; import com.clickhouse.client.api.data_formats.NativeFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryFormatReader; @@ -2057,6 +2058,8 @@ public CompletableFuture execute(String sql) { *

Create an instance of {@link ClickHouseBinaryFormatReader} based on response. Table schema is option and only * required for {@link ClickHouseFormat#RowBinaryWithNames}, {@link ClickHouseFormat#RowBinary}. * Format {@link ClickHouseFormat#RowBinaryWithDefaults} is not supported for output (read operations).

+ *

This factory only accepts binary output formats. For text formats such as + * {@link ClickHouseFormat#JSONEachRow} use {@link #newTextFormatReader(QueryResponse)} instead.

* @param response * @param schema * @return @@ -2083,11 +2086,6 @@ public ClickHouseBinaryFormatReader newBinaryFormatReader(QueryResponse response reader = new RowBinaryFormatReader(response.getInputStream(), response.getSettings(), schema, byteBufferPool, typeHintMapping); break; - case JSONEachRow: - String jsonProcessor = ClientConfigProperties.JSON_PROCESSOR.getOrDefault(configuration); - JsonParser parser = JsonParserFactory.createParser(jsonProcessor, response.getInputStream()); - reader = new JSONEachRowFormatReader(parser); - break; default: throw new IllegalArgumentException("Binary readers doesn't support format: " + response.getFormat()); } @@ -2098,6 +2096,28 @@ public ClickHouseBinaryFormatReader newBinaryFormatReader(QueryResponse response return newBinaryFormatReader(response, null); } + /** + *

Create an instance of {@link ClickHouseTextFormatReader} based on response. Currently supports + * {@link ClickHouseFormat#JSONEachRow}; the concrete row parser is selected through the + * {@link ClientConfigProperties#JSON_PROCESSOR} configuration option.

+ *

For binary output formats use + * {@link #newBinaryFormatReader(QueryResponse)} or + * {@link #newBinaryFormatReader(QueryResponse, TableSchema)} instead.

+ * @param response query response whose stream will be consumed by the reader + * @return text format reader matching {@code response.getFormat()} + * @throws IllegalArgumentException if the response format is not a supported text format + */ + public ClickHouseTextFormatReader newTextFormatReader(QueryResponse response) { + switch (response.getFormat()) { + case JSONEachRow: + String jsonProcessor = ClientConfigProperties.JSON_PROCESSOR.getOrDefault(configuration); + JsonParser parser = JsonParserFactory.createParser(jsonProcessor, response.getInputStream()); + return new JSONEachRowFormatReader(parser); + default: + throw new IllegalArgumentException("Text readers doesn't support format: " + response.getFormat()); + } + } + private String registerOperationMetrics() { String operationId = UUID.randomUUID().toString(); globalClientStats.put(operationId, new ClientStatisticsHolder()); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReader.java index 51e1b1df0..c95bde792 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReader.java @@ -1,618 +1,21 @@ package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.metadata.TableSchema; -import com.clickhouse.data.value.ClickHouseBitmap; -import com.clickhouse.data.value.ClickHouseGeoMultiPolygonValue; -import com.clickhouse.data.value.ClickHouseGeoPointValue; -import com.clickhouse.data.value.ClickHouseGeoPolygonValue; -import com.clickhouse.data.value.ClickHouseGeoRingValue; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.time.temporal.TemporalAmount; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public interface ClickHouseBinaryFormatReader extends AutoCloseable { - - /** - * Reads a single value from the stream. - * - * @param - * @return - */ - T readValue(int colIndex); - - /** - * Reads a row to an array of objects. - * - * @param colName - * @param - * @return - */ - T readValue(String colName); - - boolean hasValue(String colName); - - boolean hasValue(int colIndex); - - /** - * Checks if there are more rows to read. - * - * @return - */ - boolean hasNext(); - - /** - * Moves cursor to the next row. Must be called before reading the first row. Returns reference to - * an internal record representation. It means that next call to the method will affect value in returned Map. - * This is done for memory usage optimization. - * Method is intended to be used only by the client not an application. - * - * @return reference to a map filled with column values or null if no more records are available - */ - Map next(); - - /** - * Reads column with name `colName` as a string. - * - * @param colName - column name - * @return - */ - String getString(String colName); - - /** - * Reads column with name `colName` as a byte. - * - * @param colName - column name - * @return - */ - byte getByte(String colName); - - /** - * Reads column with name `colName` as a short. - * - * @param colName - column name - * @return - */ - short getShort(String colName); - - /** - * Reads column with name `colName` as an integer. - * - * @param colName - column name - * @return - */ - int getInteger(String colName); - - /** - * Reads column with name `colName` as a long. - * - * @param colName - column name - * @return - */ - long getLong(String colName); - - /** - * Reads column with name `colName` as a float. - * Warning: this method may lose precision for float values. - * - * @param colName - * @return - */ - float getFloat(String colName); - - /** - * Reads column with name `colName` as a double. - * Warning: this method may lose precision for double values. - * - * @param colName - * @return - */ - double getDouble(String colName); - - /** - * Reads column with name `colName` as a boolean. - * - * @param colName - * @return - */ - boolean getBoolean(String colName); - - /** - * Reads column with name `colName` as a BigInteger. - * - * @param colName - * @return - */ - BigInteger getBigInteger(String colName); - - /** - * Reads column with name `colName` as a BigDecimal. - * - * @param colName - * @return - */ - BigDecimal getBigDecimal(String colName); - - /** - * Returns the value of the specified column as an Instant. Timezone is derived from the column definition. - * If no timezone is specified in the column definition then UTC will be used. - * - * If column value is Date or Date32 it will return an Instant with time set to 00:00:00. - * If column value is DateTime or DateTime32 it will return an Instant with the time part. - * - * @param colName - * @return - */ - Instant getInstant(String colName); - - /** - * Returns the value of the specified column as a ZonedDateTime. Timezone is derived from the column definition. - * If no timezone is specified in the column definition then UTC will be used. - * - * If column value is Date or Date32 it will return a ZonedDateTime with time set to 00:00:00. - * If column value is DateTime or DateTime32 it will return a ZonedDateTime with the time part. - * - * @param colName - * @return - */ - ZonedDateTime getZonedDateTime(String colName); - - /** - * Returns the value of the specified column as a Duration. - * - * If a stored value is bigger than Long.MAX_VALUE then exception will be thrown. In such case - * use asBigInteger() method. - * - * If value of IntervalQuarter then Duration will be in the unit of Months. - * - * @param colName - * @return Duration in the unit of column type. - */ - Duration getDuration(String colName); - - - /** - * Returns the value of the specified column as an Inet4Address. - * - * @param colName - * @return - */ - Inet4Address getInet4Address(String colName); - - /** - * Returns the value of the specified column as an Inet6Address. - * - * @param colName - * @return - */ - Inet6Address getInet6Address(String colName); - - /** - * Returns the value of the specified column as a UUID. - * - * @param colName - * @return - */ - UUID getUUID(String colName); - - /** - * Returns the value of the specified column as a ClickHouseGeoPointValue. - * - * @param colName - * @return - */ - ClickHouseGeoPointValue getGeoPoint(String colName); - - /** - * Returns the value of the specified column as a ClickHouseGeoRingValue. - * - * @param colName - * @return - */ - ClickHouseGeoRingValue getGeoRing(String colName); - - /** - * Returns the value of the specified column as a ClickHouseGeoPolygonValue. - * - * @param colName - * @return - */ - ClickHouseGeoPolygonValue getGeoPolygon(String colName); - - /** - * Returns the value of the specified column as a ClickHouseGeoMultiPolygonValue. - * - * @param colName - * @return - */ - ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName); - - /** - * @see #getList(int) - * @param colName - column name - * @return list of values, or {@code null} if the value is null - */ - List getList(String colName); - - /** - * @see #getByteArray(int) - * @param colName - column name - * @return array of bytes, or {@code null} if the value is null - */ - byte[] getByteArray(String colName); - - /** - * @see #getIntArray(int) - * @param colName - column name - * @return array of int values, or {@code null} if the value is null - */ - int[] getIntArray(String colName); - - /** - * @see #getLongArray(int) - * @param colName - column name - * @return array of long values, or {@code null} if the value is null - */ - long[] getLongArray(String colName); - - /** - * @see #getFloatArray(int) - * @param colName - column name - * @return array of float values, or {@code null} if the value is null - */ - float[] getFloatArray(String colName); - - /** - * @see #getDoubleArray(int) - * @param colName - column name - * @return array of double values, or {@code null} if the value is null - */ - double[] getDoubleArray(String colName); - - /** - * @see #getBooleanArray(int) - * @param colName - column name - * @return array of boolean values, or {@code null} if the value is null - */ - boolean[] getBooleanArray(String colName); - - /** - * @see #getShortArray(int) - * @param colName - column name - * @return array of short values, or {@code null} if the value is null - */ - short[] getShortArray(String colName); - - /** - * @see #getStringArray(int) - * @param colName - column name - * @return array of string values, or {@code null} if the value is null - */ - String[] getStringArray(String colName); - - /** - * @see #getObjectArray(int) - * @param colName - column name - * @return array of objects, or {@code null} if the value is null - */ - Object[] getObjectArray(String colName); - - /** - * Reads column with name `colName` as a string. - * - * @param index - * @return - */ - String getString(int index); - - /** - * Reads column with name `colName` as a byte. - * - * @param index - * @return - */ - byte getByte(int index); - - /** - * Reads column with name `colName` as a short. - * - * @param index - * @return - */ - short getShort(int index); - - /** - * Reads column with name `colName` as an integer. - * - * @param index - * @return - */ - int getInteger(int index); - - /** - * Reads column with name `colName` as a long. - * - * @param index - * @return - */ - long getLong(int index); - - /** - * Reads column with name `colName` as a float. - * Warning: this method may lose precision for float values. - * - * @param index - * @return - */ - float getFloat(int index); - - /** - * Reads column with name `colName` as a double. - * Warning: this method may lose precision for double values. - * - * @param index - * @return - */ - double getDouble(int index); - - /** - * Reads column with name `colName` as a boolean. - * - * @param index - * @return - */ - boolean getBoolean(int index); - - /** - * Reads column with name `colName` as a BigInteger. - * - * @param index - * @return - */ - BigInteger getBigInteger(int index); - - /** - * Reads column with name `colName` as a BigDecimal. - * - * @param index - * @return - */ - BigDecimal getBigDecimal(int index); - - /** - * Returns the value of the specified column as an Instant. Timezone is derived from the column definition. - * If no timezone is specified in the column definition then UTC will be used. - * - * If column value is Date or Date32 it will return an Instant with time set to 00:00:00. - * If column value is DateTime or DateTime32 it will return an Instant with the time part. - * - * @param index - * @return - */ - Instant getInstant(int index); - - /** - * Returns the value of the specified column as a ZonedDateTime. Timezone is derived from the column definition. - * If no timezone is specified in the column definition then UTC will be used. - * - * If column value is Date or Date32 it will return a ZonedDateTime with time set to 00:00:00. - * If column value is DateTime or DateTime32 it will return a ZonedDateTime with the time part. - * - * @param index - * @return - */ - ZonedDateTime getZonedDateTime(int index); - - /** - * Returns the value of the specified column as a Duration. - * If a stored value is bigger than Long.MAX_VALUE then exception will be thrown. In such case - * use asBigInteger() method. - * If value of IntervalQuarter then Duration will be in the unit of Months. - * - * @param index - * @return Duration in the unit of column type. - */ - Duration getDuration(int index); - - - /** - * Returns the value of the specified column as an Inet4Address. - * - * @param index - * @return - */ - Inet4Address getInet4Address(int index); - - /** - * Returns the value of the specified column as an Inet6Address. - * - * @param index - * @return - */ - Inet6Address getInet6Address(int index); - - /** - * Returns the value of the specified column as a UUID. - * - * @param index - * @return - */ - UUID getUUID(int index); - - /** - * Returns the value of the specified column as a ClickHouseGeoPointValue. - * - * @param index - * @return - */ - ClickHouseGeoPointValue getGeoPoint(int index); - - /** - * Returns the value of the specified column as a ClickHouseGeoRingValue. - * - * @param index - * @return - */ - ClickHouseGeoRingValue getGeoRing(int index); - - /** - * Returns the value of the specified column as a ClickHouseGeoPolygonValue. - * - * @param index - * @return - */ - ClickHouseGeoPolygonValue getGeoPolygon(int index); - - /** - * Returns the value of the specified column as a ClickHouseGeoMultiPolygonValue. - * - * @param index - * @return - */ - ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index); - - /** - * Returns the value of the specified column as a {@link List}. Suitable for reading Array columns of any type. - *

For nested arrays (e.g. {@code Array(Array(Int64))}), returns a {@code List>}. - * For nullable arrays (e.g. {@code Array(Nullable(Int32))}), list elements may be {@code null}.

- * - * @param index - column index (1-based) - * @return list of values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the column is not an array type - */ - List getList(int index); - - /** - * Returns the value of the specified column as a {@code byte[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of bytes, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a byte array - */ - byte[] getByteArray(int index); - - /** - * Returns the value of the specified column as an {@code int[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of int values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to an int array - */ - int[] getIntArray(int index); - - /** - * Returns the value of the specified column as a {@code long[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of long values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a long array - */ - long[] getLongArray(int index); - - /** - * Returns the value of the specified column as a {@code float[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of float values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a float array - */ - float[] getFloatArray(int index); - - /** - * Returns the value of the specified column as a {@code double[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of double values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a double array - */ - double[] getDoubleArray(int index); - - /** - * Returns the value of the specified column as a {@code boolean[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of boolean values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a boolean array - */ - boolean[] getBooleanArray(int index); - - /** - * Returns the value of the specified column as a {@code short[]}. Suitable for 1D Array columns only. - * - * @param index - column index (1-based) - * @return array of short values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a short array - */ - short[] getShortArray(int index); - - /** - * Returns the value of the specified column as a {@code String[]}. Suitable for 1D Array columns only. - * Cannot be used for none string element types. - * - * @param index - column index (1-based) - * @return array of string values, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the column is not an array type - */ - String[] getStringArray(int index); - - /** - * Returns the value of the specified column as an {@code Object[]}. Suitable for multidimensional Array columns. - * Nested arrays are recursively converted to {@code Object[]}. - * Note: result is not cached so avoid repetitive calls on same column. - * - * @param index - column index (1-based) - * @return array of objects, or {@code null} if the value is null - * @throws com.clickhouse.client.api.ClientException if the column is not an array type - */ - Object[] getObjectArray(int index); - - Object[] getTuple(int index); - - Object[] getTuple(String colName); - - byte getEnum8(String colName); - - byte getEnum8(int index); - - short getEnum16(String colName); - - short getEnum16(int index); - - LocalDate getLocalDate(String colName); - - LocalDate getLocalDate(int index); - - LocalTime getLocalTime(String colName); - - LocalTime getLocalTime(int index); - - LocalDateTime getLocalDateTime(String colName); - - LocalDateTime getLocalDateTime(int index); - - OffsetDateTime getOffsetDateTime(String colName); - - OffsetDateTime getOffsetDateTime(int index); - - TableSchema getSchema(); - - ClickHouseBitmap getClickHouseBitmap(String colName); - - ClickHouseBitmap getClickHouseBitmap(int index); - - TemporalAmount getTemporalAmount(int index); - - TemporalAmount getTemporalAmount(String colName); +/** + * Reader for ClickHouse binary output formats (such as {@code Native}, + * {@code RowBinary}, {@code RowBinaryWithNames}, and + * {@code RowBinaryWithNamesAndTypes}). + * + *

Row navigation, schema access, and typed accessors are inherited from + * {@link ClickHouseFormatReader}; this interface specializes the contract for + * binary-encoded result streams and is the type returned by the binary + * factory methods on {@link com.clickhouse.client.api.Client}. Readers for + * text-oriented output formats (for example {@code JSONEachRow}) implement + * {@link ClickHouseTextFormatReader} instead.

+ * + *

Instances are produced by + * {@link com.clickhouse.client.api.Client#newBinaryFormatReader(com.clickhouse.client.api.query.QueryResponse)} + * and + * {@link com.clickhouse.client.api.Client#newBinaryFormatReader(com.clickhouse.client.api.query.QueryResponse, com.clickhouse.client.api.metadata.TableSchema)}.

+ */ +public interface ClickHouseBinaryFormatReader extends ClickHouseFormatReader { } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseFormatReader.java new file mode 100644 index 000000000..2464798fe --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseFormatReader.java @@ -0,0 +1,629 @@ +package com.clickhouse.client.api.data_formats; + +import com.clickhouse.client.api.metadata.TableSchema; +import com.clickhouse.data.value.ClickHouseBitmap; +import com.clickhouse.data.value.ClickHouseGeoMultiPolygonValue; +import com.clickhouse.data.value.ClickHouseGeoPointValue; +import com.clickhouse.data.value.ClickHouseGeoPolygonValue; +import com.clickhouse.data.value.ClickHouseGeoRingValue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAmount; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Common contract for row-by-row format readers regardless of the underlying + * wire encoding. Implementations are produced for binary formats (see + * {@link ClickHouseBinaryFormatReader}) and text formats (see + * {@link ClickHouseTextFormatReader}). + * + *

The methods declared here describe row navigation, schema access, and + * typed accessors by column name and 1-based column index. Concrete readers + * may not support every accessor for every format; unsupported accessors are + * expected to throw {@link UnsupportedOperationException}.

+ */ +public interface ClickHouseFormatReader extends AutoCloseable { + + /** + * Reads a single value from the stream. + * + * @param + * @return + */ + T readValue(int colIndex); + + /** + * Reads a row to an array of objects. + * + * @param colName + * @param + * @return + */ + T readValue(String colName); + + boolean hasValue(String colName); + + boolean hasValue(int colIndex); + + /** + * Checks if there are more rows to read. + * + * @return + */ + boolean hasNext(); + + /** + * Moves cursor to the next row. Must be called before reading the first row. Returns reference to + * an internal record representation. It means that next call to the method will affect value in returned Map. + * This is done for memory usage optimization. + * Method is intended to be used only by the client not an application. + * + * @return reference to a map filled with column values or null if no more records are available + */ + Map next(); + + /** + * Reads column with name `colName` as a string. + * + * @param colName - column name + * @return + */ + String getString(String colName); + + /** + * Reads column with name `colName` as a byte. + * + * @param colName - column name + * @return + */ + byte getByte(String colName); + + /** + * Reads column with name `colName` as a short. + * + * @param colName - column name + * @return + */ + short getShort(String colName); + + /** + * Reads column with name `colName` as an integer. + * + * @param colName - column name + * @return + */ + int getInteger(String colName); + + /** + * Reads column with name `colName` as a long. + * + * @param colName - column name + * @return + */ + long getLong(String colName); + + /** + * Reads column with name `colName` as a float. + * Warning: this method may lose precision for float values. + * + * @param colName + * @return + */ + float getFloat(String colName); + + /** + * Reads column with name `colName` as a double. + * Warning: this method may lose precision for double values. + * + * @param colName + * @return + */ + double getDouble(String colName); + + /** + * Reads column with name `colName` as a boolean. + * + * @param colName + * @return + */ + boolean getBoolean(String colName); + + /** + * Reads column with name `colName` as a BigInteger. + * + * @param colName + * @return + */ + BigInteger getBigInteger(String colName); + + /** + * Reads column with name `colName` as a BigDecimal. + * + * @param colName + * @return + */ + BigDecimal getBigDecimal(String colName); + + /** + * Returns the value of the specified column as an Instant. Timezone is derived from the column definition. + * If no timezone is specified in the column definition then UTC will be used. + * + * If column value is Date or Date32 it will return an Instant with time set to 00:00:00. + * If column value is DateTime or DateTime32 it will return an Instant with the time part. + * + * @param colName + * @return + */ + Instant getInstant(String colName); + + /** + * Returns the value of the specified column as a ZonedDateTime. Timezone is derived from the column definition. + * If no timezone is specified in the column definition then UTC will be used. + * + * If column value is Date or Date32 it will return a ZonedDateTime with time set to 00:00:00. + * If column value is DateTime or DateTime32 it will return a ZonedDateTime with the time part. + * + * @param colName + * @return + */ + ZonedDateTime getZonedDateTime(String colName); + + /** + * Returns the value of the specified column as a Duration. + * + * If a stored value is bigger than Long.MAX_VALUE then exception will be thrown. In such case + * use asBigInteger() method. + * + * If value of IntervalQuarter then Duration will be in the unit of Months. + * + * @param colName + * @return Duration in the unit of column type. + */ + Duration getDuration(String colName); + + + /** + * Returns the value of the specified column as an Inet4Address. + * + * @param colName + * @return + */ + Inet4Address getInet4Address(String colName); + + /** + * Returns the value of the specified column as an Inet6Address. + * + * @param colName + * @return + */ + Inet6Address getInet6Address(String colName); + + /** + * Returns the value of the specified column as a UUID. + * + * @param colName + * @return + */ + UUID getUUID(String colName); + + /** + * Returns the value of the specified column as a ClickHouseGeoPointValue. + * + * @param colName + * @return + */ + ClickHouseGeoPointValue getGeoPoint(String colName); + + /** + * Returns the value of the specified column as a ClickHouseGeoRingValue. + * + * @param colName + * @return + */ + ClickHouseGeoRingValue getGeoRing(String colName); + + /** + * Returns the value of the specified column as a ClickHouseGeoPolygonValue. + * + * @param colName + * @return + */ + ClickHouseGeoPolygonValue getGeoPolygon(String colName); + + /** + * Returns the value of the specified column as a ClickHouseGeoMultiPolygonValue. + * + * @param colName + * @return + */ + ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName); + + /** + * @see #getList(int) + * @param colName - column name + * @return list of values, or {@code null} if the value is null + */ + List getList(String colName); + + /** + * @see #getByteArray(int) + * @param colName - column name + * @return array of bytes, or {@code null} if the value is null + */ + byte[] getByteArray(String colName); + + /** + * @see #getIntArray(int) + * @param colName - column name + * @return array of int values, or {@code null} if the value is null + */ + int[] getIntArray(String colName); + + /** + * @see #getLongArray(int) + * @param colName - column name + * @return array of long values, or {@code null} if the value is null + */ + long[] getLongArray(String colName); + + /** + * @see #getFloatArray(int) + * @param colName - column name + * @return array of float values, or {@code null} if the value is null + */ + float[] getFloatArray(String colName); + + /** + * @see #getDoubleArray(int) + * @param colName - column name + * @return array of double values, or {@code null} if the value is null + */ + double[] getDoubleArray(String colName); + + /** + * @see #getBooleanArray(int) + * @param colName - column name + * @return array of boolean values, or {@code null} if the value is null + */ + boolean[] getBooleanArray(String colName); + + /** + * @see #getShortArray(int) + * @param colName - column name + * @return array of short values, or {@code null} if the value is null + */ + short[] getShortArray(String colName); + + /** + * @see #getStringArray(int) + * @param colName - column name + * @return array of string values, or {@code null} if the value is null + */ + String[] getStringArray(String colName); + + /** + * @see #getObjectArray(int) + * @param colName - column name + * @return array of objects, or {@code null} if the value is null + */ + Object[] getObjectArray(String colName); + + /** + * Reads column with name `colName` as a string. + * + * @param index + * @return + */ + String getString(int index); + + /** + * Reads column with name `colName` as a byte. + * + * @param index + * @return + */ + byte getByte(int index); + + /** + * Reads column with name `colName` as a short. + * + * @param index + * @return + */ + short getShort(int index); + + /** + * Reads column with name `colName` as an integer. + * + * @param index + * @return + */ + int getInteger(int index); + + /** + * Reads column with name `colName` as a long. + * + * @param index + * @return + */ + long getLong(int index); + + /** + * Reads column with name `colName` as a float. + * Warning: this method may lose precision for float values. + * + * @param index + * @return + */ + float getFloat(int index); + + /** + * Reads column with name `colName` as a double. + * Warning: this method may lose precision for double values. + * + * @param index + * @return + */ + double getDouble(int index); + + /** + * Reads column with name `colName` as a boolean. + * + * @param index + * @return + */ + boolean getBoolean(int index); + + /** + * Reads column with name `colName` as a BigInteger. + * + * @param index + * @return + */ + BigInteger getBigInteger(int index); + + /** + * Reads column with name `colName` as a BigDecimal. + * + * @param index + * @return + */ + BigDecimal getBigDecimal(int index); + + /** + * Returns the value of the specified column as an Instant. Timezone is derived from the column definition. + * If no timezone is specified in the column definition then UTC will be used. + * + * If column value is Date or Date32 it will return an Instant with time set to 00:00:00. + * If column value is DateTime or DateTime32 it will return an Instant with the time part. + * + * @param index + * @return + */ + Instant getInstant(int index); + + /** + * Returns the value of the specified column as a ZonedDateTime. Timezone is derived from the column definition. + * If no timezone is specified in the column definition then UTC will be used. + * + * If column value is Date or Date32 it will return a ZonedDateTime with time set to 00:00:00. + * If column value is DateTime or DateTime32 it will return a ZonedDateTime with the time part. + * + * @param index + * @return + */ + ZonedDateTime getZonedDateTime(int index); + + /** + * Returns the value of the specified column as a Duration. + * If a stored value is bigger than Long.MAX_VALUE then exception will be thrown. In such case + * use asBigInteger() method. + * If value of IntervalQuarter then Duration will be in the unit of Months. + * + * @param index + * @return Duration in the unit of column type. + */ + Duration getDuration(int index); + + + /** + * Returns the value of the specified column as an Inet4Address. + * + * @param index + * @return + */ + Inet4Address getInet4Address(int index); + + /** + * Returns the value of the specified column as an Inet6Address. + * + * @param index + * @return + */ + Inet6Address getInet6Address(int index); + + /** + * Returns the value of the specified column as a UUID. + * + * @param index + * @return + */ + UUID getUUID(int index); + + /** + * Returns the value of the specified column as a ClickHouseGeoPointValue. + * + * @param index + * @return + */ + ClickHouseGeoPointValue getGeoPoint(int index); + + /** + * Returns the value of the specified column as a ClickHouseGeoRingValue. + * + * @param index + * @return + */ + ClickHouseGeoRingValue getGeoRing(int index); + + /** + * Returns the value of the specified column as a ClickHouseGeoPolygonValue. + * + * @param index + * @return + */ + ClickHouseGeoPolygonValue getGeoPolygon(int index); + + /** + * Returns the value of the specified column as a ClickHouseGeoMultiPolygonValue. + * + * @param index + * @return + */ + ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index); + + /** + * Returns the value of the specified column as a {@link List}. Suitable for reading Array columns of any type. + *

For nested arrays (e.g. {@code Array(Array(Int64))}), returns a {@code List>}. + * For nullable arrays (e.g. {@code Array(Nullable(Int32))}), list elements may be {@code null}.

+ * + * @param index - column index (1-based) + * @return list of values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the column is not an array type + */ + List getList(int index); + + /** + * Returns the value of the specified column as a {@code byte[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of bytes, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a byte array + */ + byte[] getByteArray(int index); + + /** + * Returns the value of the specified column as an {@code int[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of int values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to an int array + */ + int[] getIntArray(int index); + + /** + * Returns the value of the specified column as a {@code long[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of long values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a long array + */ + long[] getLongArray(int index); + + /** + * Returns the value of the specified column as a {@code float[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of float values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a float array + */ + float[] getFloatArray(int index); + + /** + * Returns the value of the specified column as a {@code double[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of double values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a double array + */ + double[] getDoubleArray(int index); + + /** + * Returns the value of the specified column as a {@code boolean[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of boolean values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a boolean array + */ + boolean[] getBooleanArray(int index); + + /** + * Returns the value of the specified column as a {@code short[]}. Suitable for 1D Array columns only. + * + * @param index - column index (1-based) + * @return array of short values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the value cannot be converted to a short array + */ + short[] getShortArray(int index); + + /** + * Returns the value of the specified column as a {@code String[]}. Suitable for 1D Array columns only. + * Cannot be used for none string element types. + * + * @param index - column index (1-based) + * @return array of string values, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the column is not an array type + */ + String[] getStringArray(int index); + + /** + * Returns the value of the specified column as an {@code Object[]}. Suitable for multidimensional Array columns. + * Nested arrays are recursively converted to {@code Object[]}. + * Note: result is not cached so avoid repetitive calls on same column. + * + * @param index - column index (1-based) + * @return array of objects, or {@code null} if the value is null + * @throws com.clickhouse.client.api.ClientException if the column is not an array type + */ + Object[] getObjectArray(int index); + + Object[] getTuple(int index); + + Object[] getTuple(String colName); + + byte getEnum8(String colName); + + byte getEnum8(int index); + + short getEnum16(String colName); + + short getEnum16(int index); + + LocalDate getLocalDate(String colName); + + LocalDate getLocalDate(int index); + + LocalTime getLocalTime(String colName); + + LocalTime getLocalTime(int index); + + LocalDateTime getLocalDateTime(String colName); + + LocalDateTime getLocalDateTime(int index); + + OffsetDateTime getOffsetDateTime(String colName); + + OffsetDateTime getOffsetDateTime(int index); + + TableSchema getSchema(); + + ClickHouseBitmap getClickHouseBitmap(String colName); + + ClickHouseBitmap getClickHouseBitmap(int index); + + TemporalAmount getTemporalAmount(int index); + + TemporalAmount getTemporalAmount(String colName); +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java new file mode 100644 index 000000000..1b50b7b6f --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java @@ -0,0 +1,20 @@ +package com.clickhouse.client.api.data_formats; + +/** + * Reader for ClickHouse text output formats (such as + * {@code JSONEachRow}). + * + *

Row navigation, schema access, and typed accessors are inherited from + * {@link ClickHouseFormatReader}; this interface specializes the contract for + * text-encoded result streams and is the type returned by the text factory + * method on {@link com.clickhouse.client.api.Client}. Readers for binary + * output formats implement {@link ClickHouseBinaryFormatReader} instead.

+ * + *

Instances are produced by + * {@link com.clickhouse.client.api.Client#newTextFormatReader(com.clickhouse.client.api.query.QueryResponse)}. + * Concrete readers may not support every accessor declared on + * {@link ClickHouseFormatReader}; unsupported accessors are expected to throw + * {@link UnsupportedOperationException}.

+ */ +public interface ClickHouseTextFormatReader extends ClickHouseFormatReader { +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index d0615e526..bd283fc62 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -27,7 +27,7 @@ import java.util.Map; import java.util.UUID; -public class JSONEachRowFormatReader implements ClickHouseBinaryFormatReader { +public class JSONEachRowFormatReader implements ClickHouseTextFormatReader { private final JsonParser parser; private TableSchema schema; private Map currentRow; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 1a760c334..85cdb62f9 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -53,8 +53,8 @@ public void testBasicParsing() throws Exception { "UNION ALL SELECT 2, 'clickhouse', false"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); - + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + // First row Assert.assertTrue(reader.hasNext()); Map row1 = reader.next(); @@ -62,7 +62,7 @@ public void testBasicParsing() throws Exception { Assert.assertEquals(reader.getInteger("id"), 1); Assert.assertEquals(reader.getString("name"), "test"); Assert.assertEquals(reader.getBoolean("active"), true); - + // Second row Assert.assertTrue(reader.hasNext()); Map row2 = reader.next(); @@ -70,7 +70,7 @@ public void testBasicParsing() throws Exception { Assert.assertEquals(reader.getInteger("id"), 2); Assert.assertEquals(reader.getString("name"), "clickhouse"); Assert.assertEquals(reader.getBoolean("active"), false); - + // No more rows Assert.assertNull(reader.next()); } @@ -82,11 +82,11 @@ public void testSchemaInference() throws Exception { "true as col_bool, 'val' as col_str"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); - + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + Assert.assertNotNull(reader.getSchema()); Assert.assertEquals(reader.getSchema().getColumns().size(), 4); - + Assert.assertEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.Int64); Assert.assertEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.Float64); Assert.assertEquals(reader.getSchema().getColumnByIndex(3).getDataType(), ClickHouseDataType.Bool); @@ -101,8 +101,8 @@ public void testDataTypes() throws Exception { "true as bool, 'hello' as str"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); - + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + reader.next(); Assert.assertEquals(reader.getByte("b"), (byte) 120); Assert.assertEquals(reader.getShort("s"), (short) 30000); @@ -120,11 +120,20 @@ public void testEmptyData() throws Exception { String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); - + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + Assert.assertFalse(reader.hasNext()); Assert.assertNull(reader.next()); Assert.assertEquals(reader.getSchema().getColumns().size(), 0); } } + + @Test(groups = {"integration"}, expectedExceptions = IllegalArgumentException.class) + public void testNewBinaryFormatReaderRejectsJsonEachRow() throws Exception { + String sql = "SELECT 1 as id"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { + client.newBinaryFormatReader(response); + } + } } diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 00f043756..79bb6d1ff 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -49,11 +49,23 @@ provides additional advantages beyond what the format alone delivers: `client-v2`: +- Introduces a common `com.clickhouse.client.api.data_formats.ClickHouseFormatReader` + interface that declares all row navigation, schema access, and typed + accessors. The pre-existing `ClickHouseBinaryFormatReader` becomes a + format-family sub-interface for binary output formats and inherits its + full method set unchanged from `ClickHouseFormatReader`. +- Adds `com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader`, + a sibling sub-interface for text output formats. - Adds `com.clickhouse.client.api.data_formats.JSONEachRowFormatReader`, - which implements `ClickHouseBinaryFormatReader` over a streaming JSON + which implements `ClickHouseTextFormatReader` over a streaming JSON parser. -- Extends `Client.newBinaryFormatReader(...)` to construct the reader when - `QuerySettings.getFormat() == ClickHouseFormat.JSONEachRow`. +- Adds `Client.newTextFormatReader(QueryResponse)` as the dedicated factory + for text output formats. Currently it accepts + `ClickHouseFormat.JSONEachRow` and returns a `ClickHouseTextFormatReader`. + `Client.newBinaryFormatReader(...)` continues to construct the binary + readers (`Native`, `RowBinary`, `RowBinaryWithNames`, + `RowBinaryWithNamesAndTypes`) and rejects text formats with + `IllegalArgumentException`. - Introduces an internal JSON parser SPI under `com.clickhouse.client.api.data_formats.internal`, consisting of `JsonParser`, `JsonParserFactory`, `JacksonJsonParser`, and @@ -81,25 +93,44 @@ Two runnable examples are included in the repository: ## Public API +### `ClickHouseFormatReader`, `ClickHouseBinaryFormatReader`, `ClickHouseTextFormatReader` + +```java +package com.clickhouse.client.api.data_formats; + +public interface ClickHouseFormatReader extends AutoCloseable { ... } + +public interface ClickHouseBinaryFormatReader extends ClickHouseFormatReader { } + +public interface ClickHouseTextFormatReader extends ClickHouseFormatReader { } +``` + +`ClickHouseFormatReader` is the common contract for row-by-row format +readers regardless of the underlying wire encoding. The two sub-interfaces +specialize that contract by output-format family: callers receive a +`ClickHouseBinaryFormatReader` when the response is in a binary format and a +`ClickHouseTextFormatReader` when it is in a text format. All accessor +methods declared today live on the common parent; future format-specific +extensions are expected to be added on the corresponding sub-interface +without changing the shared surface, so code written against +`ClickHouseBinaryFormatReader` continues to compile against the same +inherited methods. + ### `JSONEachRowFormatReader` ```java package com.clickhouse.client.api.data_formats; -public class JSONEachRowFormatReader implements ClickHouseBinaryFormatReader { ... } +public class JSONEachRowFormatReader implements ClickHouseTextFormatReader { ... } ``` -The reader is normally instantiated by `Client.newBinaryFormatReader(...)`. +The reader is normally instantiated by `Client.newTextFormatReader(...)`. The class is public so that callers can construct it from any `InputStream`-backed `JsonParser`. -`JSONEachRow` is a text format, but the reader currently implements -`ClickHouseBinaryFormatReader` so that existing call sites — including -`Client.newBinaryFormatReader(...)` — accept JSON output without changes. -The interface name is therefore not a precise fit for this format; a -dedicated reader interface for non-binary formats is intended to replace -this arrangement in a future release. Callers should treat the class itself -as the stable API and avoid relying on the "binary" label of the interface. +`JSONEachRow` is a text format, so the reader implements +`ClickHouseTextFormatReader`. Callers that need to handle both binary and +text readers uniformly can program against `ClickHouseFormatReader`. ### `JsonParser` SPI @@ -194,7 +225,7 @@ try (QueryResponse response = client.query( "SELECT id, name, active, score, payload FROM events ORDER BY id", settings).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); while (reader.next() != null) { int id = reader.getInteger("id"); String name = reader.getString("name"); @@ -211,9 +242,12 @@ Notes: - The reader is constructed only when the request format is `JSONEachRow`. The default request format remains `RowBinaryWithNamesAndTypes`, and callers that do not explicitly opt in are not affected. -- `client.newBinaryFormatReader(response)` returns a reader matching - `response.getFormat()`; the same call site applies to both binary and JSON - output. +- `client.newTextFormatReader(response)` returns a `ClickHouseTextFormatReader` + for text output formats. `client.newBinaryFormatReader(response)` continues + to return a `ClickHouseBinaryFormatReader` for binary output formats and + rejects text formats (such as `JSONEachRow`) with + `IllegalArgumentException`. Callers that need to handle both can program + against the shared `ClickHouseFormatReader` parent interface. - `Map` is the canonical materialization for JSON columns and for the row itself, as produced by the selected library. JSON arrays are returned as `List`; nested JSON objects are returned as nested @@ -387,6 +421,16 @@ For these types, callers should obtain the parsed value through - The default request format is unchanged. The existing binary readers (`Native`, `RowBinary`, `RowBinaryWithNames`, `RowBinaryWithNamesAndTypes`) retain their previous behavior. +- The reader hierarchy now distinguishes binary and text formats: + `ClickHouseBinaryFormatReader` and `ClickHouseTextFormatReader` are sibling + sub-interfaces of the new `ClickHouseFormatReader`. The accessor surface is + unchanged; callers that hold a `ClickHouseBinaryFormatReader` reference for + binary formats are unaffected. Callers that previously obtained a + `ClickHouseBinaryFormatReader` for `JSONEachRow` from + `Client.newBinaryFormatReader(...)` must switch to + `Client.newTextFormatReader(...)`, which returns a + `ClickHouseTextFormatReader`. `Client.newBinaryFormatReader(...)` now + rejects `JSONEachRow` with `IllegalArgumentException`. - Jackson and Gson are now declared with `provided` scope in `client-v2` and `jdbc-v2`. Applications that previously inherited Jackson transitively from these modules in `test` scope must declare the chosen processor explicitly diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index f8cd7dbe5..e3ec8f646 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -50,7 +50,7 @@ gradle run \ 2. loads sample rows from `src/main/resources/sample_data.csv` into that table; 3. reads the same rows with `runGsonExample(...)`; 4. reads the same rows again with `runJacksonExample(...)`. -- Reads rows back through `client.newBinaryFormatReader(response)` and logs the +- Reads rows back through `client.newTextFormatReader(response)` and logs the primitive columns together with the parsed JSON object from `payload`. The build keeps both `jackson-databind` and `gson` on the classpath so the diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index 2f91afa38..abb6b14f3 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -3,7 +3,7 @@ import com.clickhouse.client.api.Client; import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.command.CommandResponse; -import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.data.ClickHouseFormat; @@ -75,7 +75,7 @@ private static void runGsonExample(ConnectionConfig config) throws Exception { private static void readRows(Client client, String processor) throws Exception { try (QueryResponse response = client.query(SELECT_DATA_SQL, new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)).get()) { - ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response); while (reader.next() != null) { Map payload = reader.readValue("payload"); LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}({})", diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 4d8454e31..b2ba159b9 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -1,7 +1,7 @@ package com.clickhouse.jdbc; import com.clickhouse.client.api.DataTypeUtils; -import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.data_formats.ClickHouseFormatReader; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.data.ClickHouseColumn; @@ -51,7 +51,7 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private static final Logger log = LoggerFactory.getLogger(ResultSetImpl.class); private ResultSetMetaDataImpl metaData; - protected ClickHouseBinaryFormatReader reader; + protected ClickHouseFormatReader reader; private QueryResponse response; private boolean closed; private final StatementImpl parentStatement; @@ -73,7 +73,7 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private Consumer onDataTransferException; - public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, ClickHouseBinaryFormatReader reader, + public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, ClickHouseFormatReader reader, Consumer onDataTransferException) throws SQLException { this.parentStatement = parentStatement; this.response = response; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 66712f1e0..4211861ff 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -1,7 +1,7 @@ package com.clickhouse.jdbc; import com.clickhouse.client.api.ClientConfigProperties; -import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.data_formats.ClickHouseFormatReader; import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; @@ -178,8 +178,10 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr response = connection.getClient().query(lastStatementSql, mergedSettings).get(queryTimeout, TimeUnit.SECONDS); } - ClickHouseBinaryFormatReader reader; - if (response.getFormat() == ClickHouseFormat.JSONEachRow || !response.getFormat().isText()) { + ClickHouseFormatReader reader; + if (response.getFormat() == ClickHouseFormat.JSONEachRow) { + reader = connection.getClient().newTextFormatReader(response); + } else if (!response.getFormat().isText()) { reader = connection.getClient().newBinaryFormatReader(response); } else { throw new SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported for output format. Please check your query.", From a14030678711d07cbb3e68b5b3a4fea97cb937e6 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Mon, 27 Apr 2026 13:44:31 +0900 Subject: [PATCH 11/26] Added more tests --- .../internal/JacksonJsonParser.java | 25 +- .../AbstractJSONEachRowFormatReaderTests.java | 142 +++++- .../JSONEachRowFormatReaderTest.java | 470 ++++++++++++++++++ .../internal/JacksonJsonParserTest.java | 98 ++++ .../internal/JsonParserFactoryTest.java | 65 +++ 5 files changed, 774 insertions(+), 26 deletions(-) create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java index e406ba5cc..01f8e672c 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java @@ -1,7 +1,6 @@ package com.clickhouse.client.api.data_formats.internal; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; @@ -9,14 +8,12 @@ public class JacksonJsonParser implements com.clickhouse.client.api.data_formats.internal.JsonParser { private final ObjectMapper mapper; - private final JsonFactory factory; - private com.fasterxml.jackson.core.JsonParser parser; + private final com.fasterxml.jackson.core.JsonParser parser; public JacksonJsonParser(InputStream inputStream) { this.mapper = new ObjectMapper(); - this.factory = new JsonFactory(); try { - this.parser = factory.createParser(inputStream); + this.parser = new JsonFactory().createParser(inputStream); } catch (Exception e) { throw new RuntimeException("Failed to create Jackson parser", e); } @@ -24,26 +21,18 @@ public JacksonJsonParser(InputStream inputStream) { @Override public Map nextRow() throws Exception { + // Jackson's streaming parser skips whitespace (including the newlines that + // separate JSONEachRow objects), so reaching EOF is the only reason + // nextToken() returns null. Any non-START_OBJECT token here would indicate + // malformed input and is reported by mapper.readValue(...). if (parser.nextToken() == null) { return null; } - if (parser.currentToken() != JsonToken.START_OBJECT) { - // Handle cases where there might be extra characters between objects, - // like newlines in JSONEachRow. - while (parser.nextToken() != null && parser.currentToken() != JsonToken.START_OBJECT) { - // skip - } - if (parser.currentToken() == null) { - return null; - } - } return mapper.readValue(parser, Map.class); } @Override public void close() throws Exception { - if (parser != null) { - parser.close(); - } + parser.close(); } } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 85cdb62f9..455084641 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -16,7 +16,15 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.List; import java.util.Map; +import java.util.UUID; public abstract class AbstractJSONEachRowFormatReaderTests extends BaseIntegrationTest { @@ -52,8 +60,8 @@ public void testBasicParsing() throws Exception { String sql = "SELECT 1 as id, 'test' as name, true as active " + "UNION ALL SELECT 2, 'clickhouse', false"; - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { // First row Assert.assertTrue(reader.hasNext()); @@ -78,11 +86,13 @@ public void testBasicParsing() throws Exception { @Test(groups = {"integration"}) public void testSchemaInference() throws Exception { + // Covers all branches of guessDataType: numeric (Int64), numeric (Float64), + // Boolean (Bool) and the catch-all branch that maps strings to String. String sql = "SELECT toInt64(42) as col_int, toFloat64(3.14) as col_float, " + "true as col_bool, 'val' as col_str"; - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { Assert.assertNotNull(reader.getSchema()); Assert.assertEquals(reader.getSchema().getColumns().size(), 4); @@ -100,8 +110,8 @@ public void testDataTypes() throws Exception { "toInt64(10000000000) as l, toFloat32(1.23) as f, toFloat64(1.23456789) as d, " + "true as bool, 'hello' as str"; - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { reader.next(); Assert.assertEquals(reader.getByte("b"), (byte) 120); @@ -119,8 +129,8 @@ public void testDataTypes() throws Exception { public void testEmptyData() throws Exception { String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1"; - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get()) { - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { Assert.assertFalse(reader.hasNext()); Assert.assertNull(reader.next()); @@ -128,6 +138,122 @@ public void testEmptyData() throws Exception { } } + @Test(groups = {"integration"}) + public void testIndexedAccessors() throws Exception { + String sql = "SELECT toInt8(120) as b, toInt16(30000) as s, toInt32(1000000) as i, " + + "toInt64(10000000000) as l, toFloat32(1.5) as f, toFloat64(2.5) as d, " + + "true as bool, 'hello' as str"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + + reader.next(); + Assert.assertEquals(reader.getByte(1), (byte) 120); + Assert.assertEquals(reader.getShort(2), (short) 30000); + Assert.assertEquals(reader.getInteger(3), 1000000); + Assert.assertEquals(reader.getLong(4), 10000000000L); + Assert.assertEquals(reader.getFloat(5), 1.5f, 0.0001f); + Assert.assertEquals(reader.getDouble(6), 2.5d, 0.0001d); + Assert.assertEquals(reader.getBoolean(7), true); + Assert.assertEquals(reader.getString(8), "hello"); + Assert.assertEquals(reader.getEnum8(1), (byte) 120); + Assert.assertEquals(reader.getEnum16(2), (short) 30000); + } + } + + @Test(groups = {"integration"}) + public void testReadValueAndHasValue() throws Exception { + String sql = "SELECT 7 as id, 'abc' as name, CAST(NULL AS Nullable(String)) as missing"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + + reader.next(); + + Number id = reader.readValue("id"); + Assert.assertNotNull(id); + Assert.assertEquals(id.intValue(), 7); + Assert.assertEquals((String) reader.readValue(2), "abc"); + + Assert.assertTrue(reader.hasValue("id")); + Assert.assertTrue(reader.hasValue(2)); + Assert.assertFalse(reader.hasValue("missing")); + Assert.assertFalse(reader.hasValue(3)); + Assert.assertFalse(reader.hasValue("not_a_column")); + } + } + + @Test(groups = {"integration"}) + public void testBigNumberAccessors() throws Exception { + String sql = "SELECT toInt64(123456789012345) as bi, toDecimal64(12345.6789, 4) as bd"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + + reader.next(); + Assert.assertEquals(reader.getBigInteger("bi"), BigInteger.valueOf(123456789012345L)); + Assert.assertEquals(reader.getBigInteger(1), BigInteger.valueOf(123456789012345L)); + Assert.assertEquals(reader.getBigDecimal("bd").compareTo(new BigDecimal("12345.6789")), 0); + Assert.assertEquals(reader.getBigDecimal(2).compareTo(new BigDecimal("12345.6789")), 0); + } + } + + @Test(groups = {"integration"}) + public void testTemporalAccessors() throws Exception { + // toDate produces an ISO date string that LocalDate.parse accepts. The + // reader's getLocalDateTime / getLocalTime / getOffsetDateTime delegate + // to the JDK's default ISO parsers, so the remaining columns are + // emitted as strings already shaped to those formats. + String sql = "SELECT toDate('2024-05-06') as d, " + + "'2024-05-06T07:08:09' as dt, " + + "'09:10:11' as t, " + + "'2024-05-06T07:08:09+02:00' as odt"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + + reader.next(); + Assert.assertEquals(reader.getLocalDate("d"), LocalDate.of(2024, 5, 6)); + Assert.assertEquals(reader.getLocalDate(1), LocalDate.of(2024, 5, 6)); + Assert.assertEquals(reader.getLocalDateTime("dt"), + LocalDateTime.of(2024, 5, 6, 7, 8, 9)); + Assert.assertEquals(reader.getLocalDateTime(2), + LocalDateTime.of(2024, 5, 6, 7, 8, 9)); + Assert.assertEquals(reader.getLocalTime("t"), LocalTime.of(9, 10, 11)); + Assert.assertEquals(reader.getLocalTime(3), LocalTime.of(9, 10, 11)); + Assert.assertEquals(reader.getOffsetDateTime("odt"), + OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); + Assert.assertEquals(reader.getOffsetDateTime(4), + OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); + } + } + + @Test(groups = {"integration"}) + public void testUuidAndListAccessors() throws Exception { + String sql = "SELECT toUUID('11111111-2222-3333-4444-555555555555') as u, " + + "[1, 2, 3] as arr"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + + reader.next(); + UUID expected = UUID.fromString("11111111-2222-3333-4444-555555555555"); + Assert.assertEquals(reader.getUUID("u"), expected); + Assert.assertEquals(reader.getUUID(1), expected); + + List values = reader.getList("arr"); + Assert.assertNotNull(values); + Assert.assertEquals(values.size(), 3); + Assert.assertEquals(values.get(0).intValue(), 1); + Assert.assertEquals(values.get(1).intValue(), 2); + Assert.assertEquals(values.get(2).intValue(), 3); + + List byIndex = reader.getList(2); + Assert.assertNotNull(byIndex); + Assert.assertEquals(byIndex.size(), 3); + } + } + @Test(groups = {"integration"}, expectedExceptions = IllegalArgumentException.class) public void testNewBinaryFormatReaderRejectsJsonEachRow() throws Exception { String sql = "SELECT 1 as id"; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java new file mode 100644 index 000000000..8bdfb9f5e --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -0,0 +1,470 @@ +package com.clickhouse.client.api.data_formats; + +import com.clickhouse.client.api.data_formats.internal.JsonParser; +import com.clickhouse.data.ClickHouseDataType; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +@Test(groups = {"unit"}) +public class JSONEachRowFormatReaderTest { + + /** Simple in-memory parser that yields a fixed list of rows. */ + private static final class StubJsonParser implements JsonParser { + private final List> rows; + private boolean closed; + private int index; + + StubJsonParser(List> rows) { + this.rows = new ArrayList<>(rows); + } + + @Override + public Map nextRow() { + return index < rows.size() ? rows.get(index++) : null; + } + + @Override + public void close() { + closed = true; + } + + boolean isClosed() { + return closed; + } + } + + private static Map row(Object... pairs) { + Map r = new LinkedHashMap<>(); + for (int i = 0; i < pairs.length; i += 2) { + r.put((String) pairs[i], pairs[i + 1]); + } + return r; + } + + private static JSONEachRowFormatReader readerOf(Map... rows) { + return new JSONEachRowFormatReader(new StubJsonParser(Arrays.asList(rows))); + } + + // --------------------------------------------------------------------- + // guessDataType + // --------------------------------------------------------------------- + + @Test + public void testGuessDataTypeForIntegerLikeValuesIsInt64() { + JSONEachRowFormatReader reader = readerOf(row( + "as_integer", 1, + "as_long", 2L, + "as_big_integer", BigInteger.TEN)); + + Assert.assertEquals(reader.getSchema().getColumnByName("as_integer").getDataType(), + ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_long").getDataType(), + ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_big_integer").getDataType(), + ClickHouseDataType.Int64); + } + + @Test + public void testGuessDataTypeForFractionalDoubleIsFloat64() { + JSONEachRowFormatReader reader = readerOf(row( + "as_double", 1.5d, + "as_float", 2.5f, + "as_big_decimal", new BigDecimal("3.14"))); + + Assert.assertEquals(reader.getSchema().getColumnByName("as_double").getDataType(), + ClickHouseDataType.Float64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_float").getDataType(), + ClickHouseDataType.Float64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_big_decimal").getDataType(), + ClickHouseDataType.Float64); + } + + @Test + public void testGuessDataTypeForWholeDoubleIsInt64() { + JSONEachRowFormatReader reader = readerOf(row( + "as_double_whole", 5.0d, + "as_float_whole", 7.0f, + "as_big_decimal_whole", new BigDecimal("42"))); + + Assert.assertEquals(reader.getSchema().getColumnByName("as_double_whole").getDataType(), + ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_float_whole").getDataType(), + ClickHouseDataType.Int64); + Assert.assertEquals(reader.getSchema().getColumnByName("as_big_decimal_whole").getDataType(), + ClickHouseDataType.Int64); + } + + @Test + public void testGuessDataTypeForOutOfRangeDoubleIsFloat64() { + // Values outside the long range cannot be represented as Int64; the + // reader must fall back to Float64 even when they are mathematically + // whole numbers. + JSONEachRowFormatReader reader = readerOf(row( + "too_big", 1.0e20d, + "infinite", Double.POSITIVE_INFINITY)); + + Assert.assertEquals(reader.getSchema().getColumnByName("too_big").getDataType(), + ClickHouseDataType.Float64); + Assert.assertEquals(reader.getSchema().getColumnByName("infinite").getDataType(), + ClickHouseDataType.Float64); + } + + @Test + public void testGuessDataTypeForOtherNumberSubtypesIsFloat64() { + // AtomicInteger is a Number that is neither Integer/Long/BigInteger + // nor Double/Float/BigDecimal, so it lands in the catch-all numeric + // branch. + JSONEachRowFormatReader reader = readerOf(row("custom", new AtomicInteger(5))); + Assert.assertEquals(reader.getSchema().getColumnByName("custom").getDataType(), + ClickHouseDataType.Float64); + } + + @Test + public void testGuessDataTypeForBooleanIsBool() { + JSONEachRowFormatReader reader = readerOf(row("flag", Boolean.TRUE)); + Assert.assertEquals(reader.getSchema().getColumnByName("flag").getDataType(), + ClickHouseDataType.Bool); + } + + @Test + public void testGuessDataTypeDefaultBranchIsString() { + // Strings, lists, maps, and JSON null should all fall through to the + // catch-all branch and be reported as String columns. + JSONEachRowFormatReader reader = readerOf(row( + "as_string", "hello", + "as_list", Arrays.asList(1, 2, 3), + "as_map", Collections.singletonMap("k", "v"), + "as_null", null)); + + Assert.assertEquals(reader.getSchema().getColumnByName("as_string").getDataType(), + ClickHouseDataType.String); + Assert.assertEquals(reader.getSchema().getColumnByName("as_list").getDataType(), + ClickHouseDataType.String); + Assert.assertEquals(reader.getSchema().getColumnByName("as_map").getDataType(), + ClickHouseDataType.String); + Assert.assertEquals(reader.getSchema().getColumnByName("as_null").getDataType(), + ClickHouseDataType.String); + } + + @Test + public void testEmptyResultSetExposesEmptySchema() { + JSONEachRowFormatReader reader = new JSONEachRowFormatReader( + new StubJsonParser(Collections.emptyList())); + + Assert.assertNotNull(reader.getSchema()); + Assert.assertEquals(reader.getSchema().getColumns().size(), 0); + Assert.assertFalse(reader.hasNext()); + Assert.assertNull(reader.next()); + } + + @Test + public void testReaderInitializationWrapsParserFailures() { + JsonParser failing = new JsonParser() { + @Override + public Map nextRow() throws Exception { + throw new IllegalStateException("boom"); + } + @Override + public void close() { } + }; + try { + new JSONEachRowFormatReader(failing); + Assert.fail("Expected RuntimeException"); + } catch (RuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Failed to initialize JSON reader"), + "Unexpected message: " + e.getMessage()); + Assert.assertTrue(e.getCause() instanceof IllegalStateException); + } + } + + @Test + public void testNextRowFailureIsWrapped() throws Exception { + JsonParser parser = new JsonParser() { + private int call; + @Override + public Map nextRow() { + if (call++ == 0) { + return row("id", 1); + } + throw new IllegalStateException("kaboom"); + } + @Override + public void close() { } + }; + try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { + // First row was read eagerly during construction. + Assert.assertNotNull(reader.next()); + try { + reader.next(); + Assert.fail("Expected RuntimeException"); + } catch (RuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Failed to read next JSON row"), + "Unexpected message: " + e.getMessage()); + } + } + } + + // --------------------------------------------------------------------- + // Row navigation, readValue, hasValue + // --------------------------------------------------------------------- + + @Test + public void testHasNextAndNext() throws Exception { + Map r1 = row("id", 1); + Map r2 = row("id", 2); + + try (JSONEachRowFormatReader reader = readerOf(r1, r2)) { + Assert.assertTrue(reader.hasNext()); + Assert.assertSame(reader.next(), r1); + // After the first row has been returned, hasNext() optimistically + // returns true; callers detect the end of the stream when next() + // returns null. + Assert.assertTrue(reader.hasNext()); + Assert.assertSame(reader.next(), r2); + Assert.assertNull(reader.next()); + } + } + + @Test + public void testReadValueByIndexAndName() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row("id", 42, "name", "abc"))) { + reader.next(); + Number byIndex = reader.readValue(1); + Assert.assertNotNull(byIndex); + Assert.assertEquals(byIndex.intValue(), 42); + Assert.assertEquals((String) reader.readValue("name"), "abc"); + Assert.assertEquals((String) reader.readValue(2), "abc"); + } + } + + @Test + public void testHasValue() throws Exception { + Map r = new HashMap<>(); + r.put("present", "value"); + r.put("nullable", null); + + try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader( + new StubJsonParser(Collections.singletonList(r)))) { + reader.next(); + Assert.assertTrue(reader.hasValue("present")); + Assert.assertFalse(reader.hasValue("nullable")); + Assert.assertFalse(reader.hasValue("missing")); + // The schema only contains the keys observed in the first row, so + // any column index resolved to a name that is present must be + // truthy and any nullable column must be falsy. + Assert.assertEquals(reader.hasValue(1), reader.hasValue(reader.getSchema().columnIndexToName(1))); + } + } + + @Test + public void testCloseDelegatesToParser() throws Exception { + StubJsonParser parser = new StubJsonParser(Collections.singletonList(row("id", 1))); + JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser); + Assert.assertFalse(parser.isClosed()); + reader.close(); + Assert.assertTrue(parser.isClosed()); + } + + // --------------------------------------------------------------------- + // Typed accessors + // --------------------------------------------------------------------- + + @Test + public void testNumericAccessors() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row( + "b", 120, + "s", 30000, + "i", 1_000_000, + "l", 10_000_000_000L, + "f", 1.5d, + "d", 2.5d))) { + reader.next(); + + Assert.assertEquals(reader.getByte("b"), (byte) 120); + Assert.assertEquals(reader.getByte(1), (byte) 120); + Assert.assertEquals(reader.getShort("s"), (short) 30000); + Assert.assertEquals(reader.getShort(2), (short) 30000); + Assert.assertEquals(reader.getInteger("i"), 1_000_000); + Assert.assertEquals(reader.getInteger(3), 1_000_000); + Assert.assertEquals(reader.getLong("l"), 10_000_000_000L); + Assert.assertEquals(reader.getLong(4), 10_000_000_000L); + Assert.assertEquals(reader.getFloat("f"), 1.5f, 0.0001f); + Assert.assertEquals(reader.getFloat(5), 1.5f, 0.0001f); + Assert.assertEquals(reader.getDouble("d"), 2.5d, 0.0001d); + Assert.assertEquals(reader.getDouble(6), 2.5d, 0.0001d); + + Assert.assertEquals(reader.getEnum8("b"), (byte) 120); + Assert.assertEquals(reader.getEnum8(1), (byte) 120); + Assert.assertEquals(reader.getEnum16("s"), (short) 30000); + Assert.assertEquals(reader.getEnum16(2), (short) 30000); + } + } + + @Test + public void testStringAccessor() throws Exception { + Map r = new LinkedHashMap<>(); + r.put("s", "hello"); + r.put("missing", null); + + try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader( + new StubJsonParser(Collections.singletonList(r)))) { + reader.next(); + Assert.assertEquals(reader.getString("s"), "hello"); + Assert.assertEquals(reader.getString(1), "hello"); + Assert.assertNull(reader.getString("missing")); + Assert.assertNull(reader.getString(2)); + } + } + + @Test + public void testBooleanAccessor() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row( + "from_bool", Boolean.TRUE, + "from_zero", 0, + "from_nonzero", 1, + "from_string", "true"))) { + reader.next(); + Assert.assertTrue(reader.getBoolean("from_bool")); + Assert.assertFalse(reader.getBoolean("from_zero")); + Assert.assertTrue(reader.getBoolean("from_nonzero")); + Assert.assertTrue(reader.getBoolean("from_string")); + Assert.assertTrue(reader.getBoolean(1)); + } + } + + @Test + public void testBigNumberAccessors() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row( + "from_big_integer", new BigInteger("123456789012345"), + "from_string_int", "987654321", + "from_big_decimal", new BigDecimal("12345.6789"), + "from_string_dec", "0.5"))) { + reader.next(); + + Assert.assertEquals(reader.getBigInteger("from_big_integer"), + new BigInteger("123456789012345")); + Assert.assertEquals(reader.getBigInteger("from_string_int"), + new BigInteger("987654321")); + Assert.assertEquals(reader.getBigInteger(1), new BigInteger("123456789012345")); + Assert.assertNull(reader.getBigInteger("not_a_column")); + + Assert.assertEquals(reader.getBigDecimal("from_big_decimal").compareTo(new BigDecimal("12345.6789")), 0); + Assert.assertEquals(reader.getBigDecimal("from_string_dec").compareTo(new BigDecimal("0.5")), 0); + Assert.assertEquals(reader.getBigDecimal(3).compareTo(new BigDecimal("12345.6789")), 0); + } + } + + @Test + public void testTemporalAccessors() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row( + "d", "2024-05-06", + "t", "07:08:09", + "dt", "2024-05-06T07:08:09", + "odt", "2024-05-06T07:08:09+02:00"))) { + reader.next(); + + Assert.assertEquals(reader.getLocalDate("d"), java.time.LocalDate.of(2024, 5, 6)); + Assert.assertEquals(reader.getLocalDate(1), java.time.LocalDate.of(2024, 5, 6)); + Assert.assertEquals(reader.getLocalTime("t"), java.time.LocalTime.of(7, 8, 9)); + Assert.assertEquals(reader.getLocalTime(2), java.time.LocalTime.of(7, 8, 9)); + Assert.assertEquals(reader.getLocalDateTime("dt"), + java.time.LocalDateTime.of(2024, 5, 6, 7, 8, 9)); + Assert.assertEquals(reader.getLocalDateTime(3), + java.time.LocalDateTime.of(2024, 5, 6, 7, 8, 9)); + Assert.assertEquals(reader.getOffsetDateTime("odt"), + java.time.OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); + Assert.assertEquals(reader.getOffsetDateTime(4), + java.time.OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); + } + } + + @Test + public void testUuidAndListAccessors() throws Exception { + UUID uuid = UUID.fromString("11111111-2222-3333-4444-555555555555"); + try (JSONEachRowFormatReader reader = readerOf(row( + "u", uuid.toString(), + "arr", Arrays.asList(1, 2, 3)))) { + reader.next(); + + Assert.assertEquals(reader.getUUID("u"), uuid); + Assert.assertEquals(reader.getUUID(1), uuid); + + List list = reader.getList("arr"); + Assert.assertEquals(list, Arrays.asList(1, 2, 3)); + Assert.assertEquals(reader.getList(2), Arrays.asList(1, 2, 3)); + } + } + + // --------------------------------------------------------------------- + // Unsupported operations + // --------------------------------------------------------------------- + + @Test + public void testUnsupportedAccessorsThrow() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row("v", "x"))) { + reader.next(); + + assertUnsupported(() -> reader.getInstant("v")); + assertUnsupported(() -> reader.getInstant(1)); + assertUnsupported(() -> reader.getZonedDateTime("v")); + assertUnsupported(() -> reader.getZonedDateTime(1)); + assertUnsupported(() -> reader.getDuration("v")); + assertUnsupported(() -> reader.getDuration(1)); + assertUnsupported(() -> reader.getInet4Address("v")); + assertUnsupported(() -> reader.getInet4Address(1)); + assertUnsupported(() -> reader.getInet6Address("v")); + assertUnsupported(() -> reader.getInet6Address(1)); + assertUnsupported(() -> reader.getGeoPoint("v")); + assertUnsupported(() -> reader.getGeoPoint(1)); + assertUnsupported(() -> reader.getGeoRing("v")); + assertUnsupported(() -> reader.getGeoRing(1)); + assertUnsupported(() -> reader.getGeoPolygon("v")); + assertUnsupported(() -> reader.getGeoPolygon(1)); + assertUnsupported(() -> reader.getGeoMultiPolygon("v")); + assertUnsupported(() -> reader.getGeoMultiPolygon(1)); + assertUnsupported(() -> reader.getByteArray("v")); + assertUnsupported(() -> reader.getByteArray(1)); + assertUnsupported(() -> reader.getIntArray("v")); + assertUnsupported(() -> reader.getIntArray(1)); + assertUnsupported(() -> reader.getLongArray("v")); + assertUnsupported(() -> reader.getLongArray(1)); + assertUnsupported(() -> reader.getFloatArray("v")); + assertUnsupported(() -> reader.getFloatArray(1)); + assertUnsupported(() -> reader.getDoubleArray("v")); + assertUnsupported(() -> reader.getDoubleArray(1)); + assertUnsupported(() -> reader.getBooleanArray("v")); + assertUnsupported(() -> reader.getBooleanArray(1)); + assertUnsupported(() -> reader.getShortArray("v")); + assertUnsupported(() -> reader.getShortArray(1)); + assertUnsupported(() -> reader.getStringArray("v")); + assertUnsupported(() -> reader.getStringArray(1)); + assertUnsupported(() -> reader.getObjectArray("v")); + assertUnsupported(() -> reader.getObjectArray(1)); + assertUnsupported(() -> reader.getClickHouseBitmap("v")); + assertUnsupported(() -> reader.getClickHouseBitmap(1)); + assertUnsupported(() -> reader.getTemporalAmount("v")); + assertUnsupported(() -> reader.getTemporalAmount(1)); + } + } + + private static void assertUnsupported(Runnable r) { + try { + r.run(); + Assert.fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + // ok + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java new file mode 100644 index 000000000..0c83db2d5 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java @@ -0,0 +1,98 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.fasterxml.jackson.core.JsonParseException; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +@Test(groups = {"unit"}) +public class JacksonJsonParserTest { + + private static InputStream input(String json) { + return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + } + + @Test + public void testReadsSingleRow() throws Exception { + try (JacksonJsonParser parser = new JacksonJsonParser( + input("{\"id\": 1, \"name\": \"a\"}"))) { + Map row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(((Number) row.get("id")).intValue(), 1); + Assert.assertEquals(row.get("name"), "a"); + + // After the only row, EOF must be reported as null. + Assert.assertNull(parser.nextRow()); + } + } + + @Test + public void testReadsMultipleRowsSeparatedByWhitespace() throws Exception { + // JSONEachRow output is a sequence of JSON objects separated by + // newlines. Jackson skips whitespace between tokens, so the parser + // must transparently advance to each subsequent object. + String body = "{\"id\":1}\n{\"id\":2}\n {\"id\":3}\n"; + try (JacksonJsonParser parser = new JacksonJsonParser(input(body))) { + for (int expected : Arrays.asList(1, 2, 3)) { + Map row = parser.nextRow(); + Assert.assertNotNull(row, "row " + expected + " should not be null"); + Assert.assertEquals(((Number) row.get("id")).intValue(), expected); + } + Assert.assertNull(parser.nextRow()); + } + } + + @Test + public void testEmptyInputReturnsNull() throws Exception { + try (JacksonJsonParser parser = new JacksonJsonParser(input(""))) { + Assert.assertNull(parser.nextRow()); + } + } + + @Test + public void testWhitespaceOnlyInputReturnsNull() throws Exception { + try (JacksonJsonParser parser = new JacksonJsonParser(input(" \n\n "))) { + Assert.assertNull(parser.nextRow()); + } + } + + @Test + public void testRepeatedNextRowAfterExhaustionRemainsNull() throws Exception { + try (JacksonJsonParser parser = new JacksonJsonParser(input("{\"id\":1}"))) { + Assert.assertNotNull(parser.nextRow()); + Assert.assertNull(parser.nextRow()); + Assert.assertNull(parser.nextRow()); + } + } + + @Test(expectedExceptions = JsonParseException.class) + public void testMalformedInputPropagatesParseException() throws Exception { + try (JacksonJsonParser parser = new JacksonJsonParser(input("{not valid json"))) { + parser.nextRow(); + } + } + + @Test + public void testCloseClosesUnderlyingStream() throws Exception { + AtomicBoolean closed = new AtomicBoolean(false); + InputStream stream = new ByteArrayInputStream("{\"id\":1}".getBytes(StandardCharsets.UTF_8)) { + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + }; + + JacksonJsonParser parser = new JacksonJsonParser(stream); + parser.close(); + Assert.assertTrue(closed.get(), "Underlying input stream should be closed"); + } + +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java new file mode 100644 index 000000000..807a2d10e --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java @@ -0,0 +1,65 @@ +package com.clickhouse.client.api.data_formats.internal; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +@Test(groups = {"unit"}) +public class JsonParserFactoryTest { + + private static InputStream emptyStream() { + return new ByteArrayInputStream(new byte[0]); + } + + @Test + public void testCreateParserJackson() throws Exception { + try (JsonParser parser = JsonParserFactory.createParser("JACKSON", emptyStream())) { + Assert.assertTrue(parser instanceof JacksonJsonParser, + "Expected JacksonJsonParser but got " + parser.getClass().getName()); + } + } + + @Test + public void testCreateParserGson() throws Exception { + try (JsonParser parser = JsonParserFactory.createParser("GSON", emptyStream())) { + Assert.assertTrue(parser instanceof GsonJsonParser, + "Expected GsonJsonParser but got " + parser.getClass().getName()); + } + } + + @Test + public void testCreateParserIsCaseInsensitive() throws Exception { + try (JsonParser jackson = JsonParserFactory.createParser("jackson", emptyStream()); + JsonParser gson = JsonParserFactory.createParser("Gson", emptyStream())) { + Assert.assertTrue(jackson instanceof JacksonJsonParser); + Assert.assertTrue(gson instanceof GsonJsonParser); + } + } + + @Test + public void testCreateParserRejectsUnknownProcessor() { + try { + JsonParserFactory.createParser("FASTJSON", emptyStream()); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("FASTJSON"), + "Error message should mention the unsupported value: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("JACKSON"), + "Error message should list JACKSON as supported: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("GSON"), + "Error message should list GSON as supported: " + e.getMessage()); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testCreateParserRejectsEmptyType() { + JsonParserFactory.createParser("", emptyStream()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testCreateParserRejectsNullType() { + JsonParserFactory.createParser(null, emptyStream()); + } +} From eac6c95a59d0aeb2724a992c799092cb0714a7ee Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 19 May 2026 16:50:49 -0700 Subject: [PATCH 12/26] Implemented client-v2 part according to the spec --- .../com/clickhouse/client/api/Client.java | 30 +-------- .../client/api/ClientConfigProperties.java | 9 --- .../ClickHouseTextFormatReader.java | 12 ++-- .../data_formats/JSONEachRowFormatReader.java | 11 +++- .../{internal => }/JsonParser.java | 2 +- .../api/data_formats/JsonParserFactory.java | 17 +++++ .../data_formats/internal/GsonJsonParser.java | 39 ----------- .../internal/GsonJsonParserFactory.java | 62 ++++++++++++++++++ .../internal/JacksonJsonParser.java | 38 ----------- .../internal/JacksonJsonParserFactory.java | 52 +++++++++++++++ .../internal/JsonParserFactory.java | 27 -------- .../com/clickhouse/client/ClientTests.java | 6 +- .../AbstractJSONEachRowFormatReaderTests.java | 24 +++---- .../GsonJSONEachRowFormatReaderTests.java | 62 +++++++++++++++++- .../JSONEachRowFormatReaderTest.java | 1 - .../JacksonJSONEachRowFormatReaderTests.java | 11 +++- .../internal/JacksonJsonParserTest.java | 17 +++-- .../internal/JsonParserFactoryTest.java | 65 ------------------- docs/client-v2-json-support.md | 2 +- .../com/clickhouse/jdbc/StatementImpl.java | 2 +- .../com/clickhouse/jdbc/StatementTest.java | 5 +- 21 files changed, 247 insertions(+), 247 deletions(-) rename client-v2/src/main/java/com/clickhouse/client/api/data_formats/{internal => }/JsonParser.java (86%) create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParserFactory.java delete mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java delete mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java delete mode 100644 client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java delete mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 5230d5869..92f3db282 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -3,15 +3,12 @@ import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.command.CommandSettings; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; -import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; -import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; +import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.clickhouse.client.api.data_formats.NativeFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryWithNamesFormatReader; import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; -import com.clickhouse.client.api.data_formats.internal.JsonParser; -import com.clickhouse.client.api.data_formats.internal.JsonParserFactory; import com.clickhouse.client.api.data_formats.internal.MapBackedRecord; import com.clickhouse.client.api.data_formats.internal.ProcessParser; import com.clickhouse.client.api.enums.Protocol; @@ -269,6 +266,7 @@ public static class Builder { private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy; private Object metricRegistry = null; private Supplier queryIdGenerator; + private JsonParserFactory jsonParserFactory; public Builder() { this.endpoints = new HashSet<>(); @@ -2058,8 +2056,6 @@ public CompletableFuture execute(String sql) { *

Create an instance of {@link ClickHouseBinaryFormatReader} based on response. Table schema is option and only * required for {@link ClickHouseFormat#RowBinaryWithNames}, {@link ClickHouseFormat#RowBinary}. * Format {@link ClickHouseFormat#RowBinaryWithDefaults} is not supported for output (read operations).

- *

This factory only accepts binary output formats. For text formats such as - * {@link ClickHouseFormat#JSONEachRow} use {@link #newTextFormatReader(QueryResponse)} instead.

* @param response * @param schema * @return @@ -2096,28 +2092,6 @@ public ClickHouseBinaryFormatReader newBinaryFormatReader(QueryResponse response return newBinaryFormatReader(response, null); } - /** - *

Create an instance of {@link ClickHouseTextFormatReader} based on response. Currently supports - * {@link ClickHouseFormat#JSONEachRow}; the concrete row parser is selected through the - * {@link ClientConfigProperties#JSON_PROCESSOR} configuration option.

- *

For binary output formats use - * {@link #newBinaryFormatReader(QueryResponse)} or - * {@link #newBinaryFormatReader(QueryResponse, TableSchema)} instead.

- * @param response query response whose stream will be consumed by the reader - * @return text format reader matching {@code response.getFormat()} - * @throws IllegalArgumentException if the response format is not a supported text format - */ - public ClickHouseTextFormatReader newTextFormatReader(QueryResponse response) { - switch (response.getFormat()) { - case JSONEachRow: - String jsonProcessor = ClientConfigProperties.JSON_PROCESSOR.getOrDefault(configuration); - JsonParser parser = JsonParserFactory.createParser(jsonProcessor, response.getInputStream()); - return new JSONEachRowFormatReader(parser); - default: - throw new IllegalArgumentException("Text readers doesn't support format: " + response.getFormat()); - } - } - private String registerOperationMetrics() { String operationId = UUID.randomUUID().toString(); globalClientStats.put(operationId, new ClientStatisticsHolder()); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java index 9c40e48dd..e548a90f9 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java @@ -196,15 +196,6 @@ public Object parseValue(String value) { * See ClickHouse Docs */ CUSTOM_SETTINGS_PREFIX("custom_settings_prefix", String.class, "custom_"), - - /** - * Configures what JSON processor will be used for JSON formats. Choices: - *
    - *
  • JACKSON - uses Jackson library.
  • - *
  • GSON - uses Gson library.
  • - *
- */ - JSON_PROCESSOR("json_processor", String.class, "JACKSON"), ; private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java index 1b50b7b6f..399820e54 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseTextFormatReader.java @@ -6,15 +6,15 @@ * *

Row navigation, schema access, and typed accessors are inherited from * {@link ClickHouseFormatReader}; this interface specializes the contract for - * text-encoded result streams and is the type returned by the text factory - * method on {@link com.clickhouse.client.api.Client}. Readers for binary - * output formats implement {@link ClickHouseBinaryFormatReader} instead.

+ * text-encoded result streams

* - *

Instances are produced by - * {@link com.clickhouse.client.api.Client#newTextFormatReader(com.clickhouse.client.api.query.QueryResponse)}. - * Concrete readers may not support every accessor declared on + *

Implementation of a reader may not support every accessor declared on * {@link ClickHouseFormatReader}; unsupported accessors are expected to throw * {@link UnsupportedOperationException}.

*/ public interface ClickHouseTextFormatReader extends ClickHouseFormatReader { + + String currentRowAsString(); + + Object currentRowAsObject(); } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index bd283fc62..142adb488 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -1,6 +1,5 @@ package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.internal.JsonParser; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.data.ClickHouseDataType; @@ -117,6 +116,16 @@ public Map next() { } } + @Override + public String currentRowAsString() { + throw new UnsupportedOperationException(); + } + + @Override + public Object currentRowAsObject() { + throw new UnsupportedOperationException(); + } + @Override public String getString(String colName) { Object val = currentRow.get(colName); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java similarity index 86% rename from client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java rename to client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java index 2d02dabb3..3a61f50fa 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParser.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java @@ -1,4 +1,4 @@ -package com.clickhouse.client.api.data_formats.internal; +package com.clickhouse.client.api.data_formats; import java.util.Map; diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParserFactory.java new file mode 100644 index 000000000..94bd3e5c9 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParserFactory.java @@ -0,0 +1,17 @@ +package com.clickhouse.client.api.data_formats; + +import java.io.IOException; +import java.io.InputStream; + +public interface JsonParserFactory { + + + /** + * Implementation should create only instance of actual JSON parser. + * This method is called for each request and should avoid long initialization or + * create big objects + * @param in - stream of bytes to parse as JSON + * @return instance of {@link JsonParser} + */ + JsonParser createJsonParser(InputStream in) throws IOException; +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java deleted file mode 100644 index fe70ec93f..000000000 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.clickhouse.client.api.data_formats.internal; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -public class GsonJsonParser implements JsonParser { - private final Gson gson; - private final JsonReader reader; - - public GsonJsonParser(InputStream inputStream) { - this.gson = new Gson(); - this.reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); - this.reader.setLenient(true); // JSONEachRow needs lenient reader for multiple root objects - } - - @Override - public Map nextRow() throws Exception { - try { - if (reader.peek() == JsonToken.END_DOCUMENT) { - return null; - } - } catch (java.io.EOFException e) { - return null; - } - return gson.fromJson(reader, new TypeToken>() {}.getType()); - } - - @Override - public void close() throws Exception { - reader.close(); - } -} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java new file mode 100644 index 000000000..adf0a7b48 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java @@ -0,0 +1,62 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.clickhouse.client.api.data_formats.JsonParser; +import com.clickhouse.client.api.data_formats.JsonParserFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class GsonJsonParserFactory implements JsonParserFactory { + private final Gson gson; + + public GsonJsonParserFactory() { + GsonBuilder builder = new GsonBuilder(); + customize(builder); + builder.setLenient(); // JSONEachRow needs lenient reader for multiple root objects + this.gson = builder.create(); + } + + protected void customize(GsonBuilder builder) { + } + + @Override + public JsonParser createJsonParser(InputStream in) { + return new JsonParserImpl(gson.newJsonReader(new InputStreamReader(in, StandardCharsets.UTF_8))); + } + + private class JsonParserImpl implements JsonParser { + + private final JsonReader reader; + + public JsonParserImpl(JsonReader jsonReader) { + this.reader = jsonReader; + } + + + @Override + public Map nextRow() throws Exception { + try { + if (reader.peek() == JsonToken.END_DOCUMENT) { + return null; + } + } catch (java.io.EOFException e) { + return null; + } + + return GsonJsonParserFactory.this.gson.fromJson(reader, new TypeToken>() { + }.getType()); + } + + @Override + public void close() throws Exception { + reader.close(); + } + } +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java deleted file mode 100644 index 01f8e672c..000000000 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParser.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.clickhouse.client.api.data_formats.internal; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.InputStream; -import java.util.Map; - -public class JacksonJsonParser implements com.clickhouse.client.api.data_formats.internal.JsonParser { - private final ObjectMapper mapper; - private final com.fasterxml.jackson.core.JsonParser parser; - - public JacksonJsonParser(InputStream inputStream) { - this.mapper = new ObjectMapper(); - try { - this.parser = new JsonFactory().createParser(inputStream); - } catch (Exception e) { - throw new RuntimeException("Failed to create Jackson parser", e); - } - } - - @Override - public Map nextRow() throws Exception { - // Jackson's streaming parser skips whitespace (including the newlines that - // separate JSONEachRow objects), so reaching EOF is the only reason - // nextToken() returns null. Any non-START_OBJECT token here would indicate - // malformed input and is reported by mapper.readValue(...). - if (parser.nextToken() == null) { - return null; - } - return mapper.readValue(parser, Map.class); - } - - @Override - public void close() throws Exception { - parser.close(); - } -} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java new file mode 100644 index 000000000..697d5b728 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java @@ -0,0 +1,52 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.clickhouse.client.api.data_formats.JsonParser; +import com.clickhouse.client.api.data_formats.JsonParserFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +public class JacksonJsonParserFactory implements JsonParserFactory { + private final ObjectMapper mapper; + + public JacksonJsonParserFactory() { + this.mapper = createMapper(); + } + + protected ObjectMapper createMapper() { + return new ObjectMapper(); + } + + @Override + public JsonParser createJsonParser(InputStream in) throws IOException { + return new JsonParserImpl(mapper.createParser(in)); + } + + private class JsonParserImpl implements JsonParser { + + private final com.fasterxml.jackson.core.JsonParser parser; + + public JsonParserImpl(com.fasterxml.jackson.core.JsonParser parser) { + this.parser = parser; + } + + @Override + public Map nextRow() throws Exception { + // Jackson's streaming parser skips whitespace (including the newlines that + // separate JSONEachRow objects), so reaching EOF is the only reason + // nextToken() returns null. Any non-START_OBJECT token here would indicate + // malformed input and is reported by mapper.readValue(...). + if (parser.nextToken() == null) { + return null; + } + return mapper.readValue(parser, Map.class); + } + + @Override + public void close() throws Exception { + parser.close(); + } + } +} diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java deleted file mode 100644 index 0e86fd70a..000000000 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.clickhouse.client.api.data_formats.internal; - -import java.io.InputStream; -import java.lang.reflect.Constructor; - -public class JsonParserFactory { - public static JsonParser createParser(String type, InputStream inputStream) { - String className; - if ("JACKSON".equalsIgnoreCase(type)) { - className = "com.clickhouse.client.api.data_formats.internal.JacksonJsonParser"; - } else if ("GSON".equalsIgnoreCase(type)) { - className = "com.clickhouse.client.api.data_formats.internal.GsonJsonParser"; - } else { - throw new IllegalArgumentException("Unsupported JSON processor: " + type + ". Supported: JACKSON, GSON"); - } - - try { - Class clazz = Class.forName(className); - Constructor constructor = clazz.getConstructor(InputStream.class); - return (JsonParser) constructor.newInstance(inputStream); - } catch (ClassNotFoundException e) { - throw new RuntimeException("JSON processor class not found: " + className + ". Make sure you have the required library (Jackson or Gson) on your classpath.", e); - } catch (Exception e) { - throw new RuntimeException("Failed to instantiate JSON processor: " + type, e); - } - } -} diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 07154058c..0508d9e58 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -329,7 +329,7 @@ public void testDefaultSettings() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. } try (Client client = new Client.Builder() @@ -362,7 +362,7 @@ public void testDefaultSettings() { .setSocketSndbuf(100000) .build()) { Map config = client.getConfiguration(); - Assert.assertEquals(config.size(), 36); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb"); Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10"); Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000"); @@ -429,7 +429,7 @@ public void testWithOldDefaults() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. } } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 455084641..53a2e8c6c 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -16,6 +16,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDate; @@ -30,8 +31,6 @@ public abstract class AbstractJSONEachRowFormatReaderTests extends BaseIntegrati protected Client client; - protected abstract String getProcessor(); - @BeforeMethod(groups = {"integration"}) public void setUp() { ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); @@ -39,7 +38,6 @@ public void setUp() { .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), isCloud()) .setUsername("default") .setPassword(ClickHouseServerForTest.getPassword()) - .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), getProcessor()) .build(); } @@ -55,13 +53,15 @@ private QuerySettings newJsonEachRowSettings() { .setFormat(ClickHouseFormat.JSONEachRow); } + protected abstract ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException; + @Test(groups = {"integration"}) public void testBasicParsing() throws Exception { String sql = "SELECT 1 as id, 'test' as name, true as active " + "UNION ALL SELECT 2, 'clickhouse', false"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { // First row Assert.assertTrue(reader.hasNext()); @@ -92,7 +92,7 @@ public void testSchemaInference() throws Exception { "true as col_bool, 'val' as col_str"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { Assert.assertNotNull(reader.getSchema()); Assert.assertEquals(reader.getSchema().getColumns().size(), 4); @@ -111,7 +111,7 @@ public void testDataTypes() throws Exception { "true as bool, 'hello' as str"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); Assert.assertEquals(reader.getByte("b"), (byte) 120); @@ -130,7 +130,7 @@ public void testEmptyData() throws Exception { String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { Assert.assertFalse(reader.hasNext()); Assert.assertNull(reader.next()); @@ -145,7 +145,7 @@ public void testIndexedAccessors() throws Exception { "true as bool, 'hello' as str"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); Assert.assertEquals(reader.getByte(1), (byte) 120); @@ -166,7 +166,7 @@ public void testReadValueAndHasValue() throws Exception { String sql = "SELECT 7 as id, 'abc' as name, CAST(NULL AS Nullable(String)) as missing"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); @@ -188,7 +188,7 @@ public void testBigNumberAccessors() throws Exception { String sql = "SELECT toInt64(123456789012345) as bi, toDecimal64(12345.6789, 4) as bd"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); Assert.assertEquals(reader.getBigInteger("bi"), BigInteger.valueOf(123456789012345L)); @@ -210,7 +210,7 @@ public void testTemporalAccessors() throws Exception { "'2024-05-06T07:08:09+02:00' as odt"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); Assert.assertEquals(reader.getLocalDate("d"), LocalDate.of(2024, 5, 6)); @@ -234,7 +234,7 @@ public void testUuidAndListAccessors() throws Exception { "[1, 2, 3] as arr"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = client.newTextFormatReader(response)) { + ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); UUID expected = UUID.fromString("11111111-2222-3333-4444-555555555555"); diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java index 58573da0c..892e8c36f 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java @@ -1,11 +1,69 @@ package com.clickhouse.client.api.data_formats; +import com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory; +import com.clickhouse.client.api.insert.InsertResponse; +import com.clickhouse.client.api.insert.InsertSettings; +import com.clickhouse.client.api.query.QueryResponse; +import com.clickhouse.data.ClickHouseFormat; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; +import lombok.AllArgsConstructor; +import lombok.Data; import org.testng.annotations.Test; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Collections; +import java.util.Map; + @Test(groups = {"integration"}) public class GsonJSONEachRowFormatReaderTests extends AbstractJSONEachRowFormatReaderTests { + + private static Gson gson = new GsonBuilder().create(); + + private JsonParserFactory parserFactory = new GsonJsonParserFactory(); + @Override - protected String getProcessor() { - return "GSON"; + protected ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException { + return new JSONEachRowFormatReader(parserFactory.createJsonParser(response.getInputStream())); + } + + @Test(groups = {"integration"}) + public void testRowToObject() throws Exception { + + TestDTO_1[] data = new TestDTO_1[] { + new TestDTO_1("key1", 0.2, -0.2, Collections.singletonMap("p1", 10)), + new TestDTO_1("key2", 0.3, -0.5, Collections.singletonMap("p1", 9)), + }; + + final String table = "test_row_to_object_json"; + final String createStmt = "CREATE TABLE IF NOT EXISTS" + table + " (key String, sensor1 Decimal, sensor2 Decimal, params JSON) Engine MergeTree Order By (key)"; + client.execute(createStmt).get().close(); + client.execute("TRUNCATE " + table).get().close(); + + try (InsertResponse response = client.insert(table, out -> { + try (JsonWriter jsonWriter = gson.newJsonWriter(new OutputStreamWriter(out))) { + jsonWriter.setLenient(true); + for (TestDTO_1 value : data) { + gson.toJson(value, TestDTO_1.class, jsonWriter); + } + } + }, ClickHouseFormat.JSONEachRow, new InsertSettings()).get()) { + System.out.println("inserted rows" + response.getWrittenRows() ); + } + } + + @Data + @AllArgsConstructor + public static class TestDTO_1 { + + private String key; + + private Double sensor1; + + private Double sensor2; + + private Map params; } } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index 8bdfb9f5e..8aaacdeb8 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -1,6 +1,5 @@ package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.internal.JsonParser; import com.clickhouse.data.ClickHouseDataType; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java index 8689504bb..47e74d44f 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java @@ -1,13 +1,18 @@ package com.clickhouse.client.api.data_formats; -import org.testng.Assert; +import com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory; +import com.clickhouse.client.api.query.QueryResponse; import org.testng.annotations.Test; +import java.io.IOException; + @Test(groups = {"integration"}) public class JacksonJSONEachRowFormatReaderTests extends AbstractJSONEachRowFormatReaderTests { + private JsonParserFactory parserFactory = new JacksonJsonParserFactory(); + @Override - protected String getProcessor() { - return "JACKSON"; + protected ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException { + return new JSONEachRowFormatReader(parserFactory.createJsonParser(response.getInputStream())); } } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java index 0c83db2d5..0ee73d2a4 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java @@ -1,5 +1,6 @@ package com.clickhouse.client.api.data_formats.internal; +import com.clickhouse.client.api.data_formats.JsonParser; import com.fasterxml.jackson.core.JsonParseException; import org.testng.Assert; import org.testng.annotations.Test; @@ -15,13 +16,15 @@ @Test(groups = {"unit"}) public class JacksonJsonParserTest { + private JacksonJsonParserFactory parserFactory = new JacksonJsonParserFactory(); + private static InputStream input(String json) { return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); } @Test public void testReadsSingleRow() throws Exception { - try (JacksonJsonParser parser = new JacksonJsonParser( + try (JsonParser parser = parserFactory.createJsonParser( input("{\"id\": 1, \"name\": \"a\"}"))) { Map row = parser.nextRow(); Assert.assertNotNull(row); @@ -39,7 +42,7 @@ public void testReadsMultipleRowsSeparatedByWhitespace() throws Exception { // newlines. Jackson skips whitespace between tokens, so the parser // must transparently advance to each subsequent object. String body = "{\"id\":1}\n{\"id\":2}\n {\"id\":3}\n"; - try (JacksonJsonParser parser = new JacksonJsonParser(input(body))) { + try (JsonParser parser = parserFactory.createJsonParser(input(body))) { for (int expected : Arrays.asList(1, 2, 3)) { Map row = parser.nextRow(); Assert.assertNotNull(row, "row " + expected + " should not be null"); @@ -51,21 +54,21 @@ public void testReadsMultipleRowsSeparatedByWhitespace() throws Exception { @Test public void testEmptyInputReturnsNull() throws Exception { - try (JacksonJsonParser parser = new JacksonJsonParser(input(""))) { + try (JsonParser parser = parserFactory.createJsonParser(input(""))) { Assert.assertNull(parser.nextRow()); } } @Test public void testWhitespaceOnlyInputReturnsNull() throws Exception { - try (JacksonJsonParser parser = new JacksonJsonParser(input(" \n\n "))) { + try (JsonParser parser = parserFactory.createJsonParser(input(" \n\n "))) { Assert.assertNull(parser.nextRow()); } } @Test public void testRepeatedNextRowAfterExhaustionRemainsNull() throws Exception { - try (JacksonJsonParser parser = new JacksonJsonParser(input("{\"id\":1}"))) { + try (JsonParser parser = parserFactory.createJsonParser(input("{\"id\":1}"))) { Assert.assertNotNull(parser.nextRow()); Assert.assertNull(parser.nextRow()); Assert.assertNull(parser.nextRow()); @@ -74,7 +77,7 @@ public void testRepeatedNextRowAfterExhaustionRemainsNull() throws Exception { @Test(expectedExceptions = JsonParseException.class) public void testMalformedInputPropagatesParseException() throws Exception { - try (JacksonJsonParser parser = new JacksonJsonParser(input("{not valid json"))) { + try (JsonParser parser = parserFactory.createJsonParser(input("{not valid json"))) { parser.nextRow(); } } @@ -90,7 +93,7 @@ public void close() throws IOException { } }; - JacksonJsonParser parser = new JacksonJsonParser(stream); + JsonParser parser = parserFactory.createJsonParser(stream); parser.close(); Assert.assertTrue(closed.get(), "Underlying input stream should be closed"); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java deleted file mode 100644 index 807a2d10e..000000000 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JsonParserFactoryTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.clickhouse.client.api.data_formats.internal; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -@Test(groups = {"unit"}) -public class JsonParserFactoryTest { - - private static InputStream emptyStream() { - return new ByteArrayInputStream(new byte[0]); - } - - @Test - public void testCreateParserJackson() throws Exception { - try (JsonParser parser = JsonParserFactory.createParser("JACKSON", emptyStream())) { - Assert.assertTrue(parser instanceof JacksonJsonParser, - "Expected JacksonJsonParser but got " + parser.getClass().getName()); - } - } - - @Test - public void testCreateParserGson() throws Exception { - try (JsonParser parser = JsonParserFactory.createParser("GSON", emptyStream())) { - Assert.assertTrue(parser instanceof GsonJsonParser, - "Expected GsonJsonParser but got " + parser.getClass().getName()); - } - } - - @Test - public void testCreateParserIsCaseInsensitive() throws Exception { - try (JsonParser jackson = JsonParserFactory.createParser("jackson", emptyStream()); - JsonParser gson = JsonParserFactory.createParser("Gson", emptyStream())) { - Assert.assertTrue(jackson instanceof JacksonJsonParser); - Assert.assertTrue(gson instanceof GsonJsonParser); - } - } - - @Test - public void testCreateParserRejectsUnknownProcessor() { - try { - JsonParserFactory.createParser("FASTJSON", emptyStream()); - Assert.fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("FASTJSON"), - "Error message should mention the unsupported value: " + e.getMessage()); - Assert.assertTrue(e.getMessage().contains("JACKSON"), - "Error message should list JACKSON as supported: " + e.getMessage()); - Assert.assertTrue(e.getMessage().contains("GSON"), - "Error message should list GSON as supported: " + e.getMessage()); - } - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testCreateParserRejectsEmptyType() { - JsonParserFactory.createParser("", emptyStream()); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testCreateParserRejectsNullType() { - JsonParserFactory.createParser(null, emptyStream()); - } -} diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 79bb6d1ff..f61c5aa3a 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -202,7 +202,7 @@ When the configured processor is not present on the classpath at the time a `RuntimeException` with the following message: ```text -JSON processor class not found: com.clickhouse.client.api.data_formats.internal.JacksonJsonParser. +JSON processor class not found: com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory. Make sure you have the required library (Jackson or Gson) on your classpath. ``` diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 4211861ff..778cf54b9 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -180,7 +180,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr ClickHouseFormatReader reader; if (response.getFormat() == ClickHouseFormat.JSONEachRow) { - reader = connection.getClient().newTextFormatReader(response); + reader = null; // TODO: } else if (!response.getFormat().isText()) { reader = connection.getClient().newBinaryFormatReader(response); } else { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 6658a1e3e..e04feb5c2 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -9,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; -import org.testng.SkipException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -650,7 +649,7 @@ public void testTextFormatInResponse() throws Exception { @Test(groups = {"integration"}) public void testJSONEachRowFormat() throws Exception { Properties properties = new Properties(); - properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); + properties.setProperty(ClientConfigProperties.JSON_PARSER_FACTORY.getKey(), "JACKSON"); try (Connection conn = getJdbcConnection(properties)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str FORMAT JSONEachRow")) { @@ -666,7 +665,7 @@ public void testJSONEachRowFormat() throws Exception { @Test(groups = {"integration"}) public void testJSONEachRowFormatGson() throws Exception { Properties properties = new Properties(); - properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "GSON"); + properties.setProperty(ClientConfigProperties.JSON_PARSER_FACTORY.getKey(), "GSON"); try (Connection conn = getJdbcConnection(properties)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT 2 AS num, 'gson' AS str FORMAT JSONEachRow")) { From e848f38b89fbdf4b03be92d00ead481a712ac161 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 19 May 2026 19:36:45 -0700 Subject: [PATCH 13/26] implemented support in JDBC --- .../GsonJSONEachRowFormatReaderTests.java | 2 +- .../com/clickhouse/jdbc/ConnectionImpl.java | 35 ++++++++++++++++++- .../com/clickhouse/jdbc/DriverProperties.java | 8 +++++ .../com/clickhouse/jdbc/StatementImpl.java | 3 +- .../com/clickhouse/jdbc/StatementTest.java | 29 +++++++-------- 5 files changed, 57 insertions(+), 20 deletions(-) diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java index 892e8c36f..769ee7b17 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java @@ -38,7 +38,7 @@ public void testRowToObject() throws Exception { }; final String table = "test_row_to_object_json"; - final String createStmt = "CREATE TABLE IF NOT EXISTS" + table + " (key String, sensor1 Decimal, sensor2 Decimal, params JSON) Engine MergeTree Order By (key)"; + final String createStmt = "CREATE TABLE IF NOT EXISTS " + table + " (key String, sensor1 Decimal, sensor2 Decimal, params JSON) Engine MergeTree Order By (key)"; client.execute(createStmt).get().close(); client.execute("TRUNCATE " + table).get().close(); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 4f41865ce..13822a2ff 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -2,6 +2,7 @@ import com.clickhouse.client.api.Client; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.client.api.query.GenericRecord; import com.clickhouse.client.api.query.QuerySettings; @@ -16,6 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.InvocationTargetException; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; @@ -69,6 +71,8 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper { private final FeatureManager featureManager; + private final JsonParserFactory jsonParserFactory; + public ConnectionImpl(String url, Properties info) throws SQLException { try { this.url = url;//Raw URL @@ -119,6 +123,10 @@ public ConnectionImpl(String url, Properties info) throws SQLException { this.sqlParser = SqlParserFacade.getParser(config.getDriverProperty(DriverProperties.SQL_PARSER.getKey(), DriverProperties.SQL_PARSER.getDefaultValue()), config); this.featureManager = new FeatureManager(this.config); + + final String jsonParserFactoryName = config.getDriverProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), null); + this.jsonParserFactory = jsonParserFactoryName == null ? null : instantiateJsonParserFactory( + config.getDriverProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), null)); } catch (SQLException e) { throw e; } catch (Exception e) { @@ -126,6 +134,31 @@ public ConnectionImpl(String url, Properties info) throws SQLException { } } + private JsonParserFactory instantiateJsonParserFactory(String className) throws SQLException { + if (className == null || className.trim().isEmpty()) { + throw new SQLException("Value of '" + DriverProperties.JSON_PARSER_FACTORY.getKey() + + "' is empty string but should be a FQN of factory class."); + } + try { + Class factoryClass = this.getClass().getClassLoader().loadClass(className); + if (!JsonParserFactory.class.isAssignableFrom(factoryClass)) { + throw new SQLException("Class '" + className + "' should implement " + JsonParserFactory.class.getName()); + } + + return (JsonParserFactory) factoryClass.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException e) { + throw new SQLException("Class '" + className + "' (implementing JsonParserFactory ) not found. Check " + + DriverProperties.JSON_PARSER_FACTORY.getKey() + " property", e); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | + NoSuchMethodException e) { + throw new SQLException("Failed to instantiate '" + className + "'. Check class implementation.", e); + } + } + + public JsonParserFactory getJsonParserFactory() { + return jsonParserFactory; + } + public SqlParserFacade getSqlParser() { return sqlParser; } @@ -534,7 +567,7 @@ public Properties getClientInfo() throws SQLException { * Creating multilevel arrays may be confusing. * Spec doesn't tell much about it so there may be different variants. * Note: createArrayOf() expect type name be for element of the array and for - * Array(Array(Int8)) it should be Int8 according to spec. However element type + * Array(Array(Int8)) it should be Int8 according to spec. However, element type * of 1st level array is Array(Int8) * @param typeName the SQL name of the type the elements of the array map to. The typeName is a * database-specific name which may be the name of a built-in type, a user-defined type or a standard SQL type supported by this database. This diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 7ec1e6823..a6185983c 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -80,6 +80,7 @@ public enum DriverProperties { /** * Controls logic of saving roles that were set using {@code SET } statement. + * Default: true - save roles */ REMEMBER_LAST_SET_ROLES("remember_last_set_roles", String.valueOf(Boolean.TRUE)), @@ -128,6 +129,13 @@ public enum DriverProperties { */ CLUSTER_NAME("jdbc_cluster_name", null), + /** + * Defines what {@link com.clickhouse.client.api.data_formats.JsonParserFactory} implementation connection + * should use when response is in {@code JSONEachRow} format. Value is the name of the factory class. Driver + * will + */ + JSON_PARSER_FACTORY("jdbc_json_parser_factor", null), + ; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 778cf54b9..a9df54662 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -2,6 +2,7 @@ import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.data_formats.ClickHouseFormatReader; +import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; @@ -180,7 +181,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr ClickHouseFormatReader reader; if (response.getFormat() == ClickHouseFormat.JSONEachRow) { - reader = null; // TODO: + reader = new JSONEachRowFormatReader(connection.getJsonParserFactory().createJsonParser(response.getInputStream())); } else if (!response.getFormat().isText()) { reader = connection.getClient().newBinaryFormatReader(response); } else { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 9655e53ec..76b428f45 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -1,6 +1,9 @@ package com.clickhouse.jdbc; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.data_formats.JsonParserFactory; +import com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory; +import com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory; import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.GenericRecord; import com.clickhouse.data.ClickHouseVersion; @@ -718,10 +721,10 @@ public void testTextFormatInResponse() throws Exception { } } - @Test(groups = {"integration"}) - public void testJSONEachRowFormat() throws Exception { + @Test(groups = {"integration"}, dataProvider = "testJSONEachRowFormatDP") + public void testJSONEachRowFormat(Class parserFactory) throws Exception { Properties properties = new Properties(); - properties.setProperty(ClientConfigProperties.JSON_PARSER_FACTORY.getKey(), "JACKSON"); + properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), parserFactory.getName()); try (Connection conn = getJdbcConnection(properties)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str FORMAT JSONEachRow")) { @@ -734,20 +737,12 @@ public void testJSONEachRowFormat() throws Exception { } } - @Test(groups = {"integration"}) - public void testJSONEachRowFormatGson() throws Exception { - Properties properties = new Properties(); - properties.setProperty(ClientConfigProperties.JSON_PARSER_FACTORY.getKey(), "GSON"); - try (Connection conn = getJdbcConnection(properties)) { - try (Statement stmt = conn.createStatement()) { - try (ResultSet rs = stmt.executeQuery("SELECT 2 AS num, 'gson' AS str FORMAT JSONEachRow")) { - assertTrue(rs.next()); - assertEquals(rs.getInt("num"), 2); - assertEquals(rs.getString("str"), "gson"); - assertFalse(rs.next()); - } - } - } + @DataProvider + public static Object[][] testJSONEachRowFormatDP() { + return new Object[][] { + {JacksonJsonParserFactory.class}, + {GsonJsonParserFactory.class}, + }; } @Test(groups = "integration") From 295b9c601110c482d1edfa18a22466e822cd9e36 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 19 May 2026 20:07:38 -0700 Subject: [PATCH 14/26] fixed tests to be more stable and test more --- .../AbstractJSONEachRowFormatReaderTests.java | 124 ++++++++++++++---- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 53a2e8c6c..c162621ae 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -23,6 +23,8 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; @@ -57,31 +59,109 @@ private QuerySettings newJsonEachRowSettings() { @Test(groups = {"integration"}) public void testBasicParsing() throws Exception { - String sql = "SELECT 1 as id, 'test' as name, true as active " + - "UNION ALL SELECT 2, 'clickhouse', false"; + String table = "test_basic_parsing"; + + List> expected = Arrays.asList( + row(1, "test", true, + Arrays.asList("a", "b", "c"), + mapOf("x", 10, "y", 20)), + row(2, "clickhouse", false, + Collections.singletonList("d"), + mapOf("z", 30))); + + client.execute("DROP TABLE IF EXISTS " + table).get(); + client.execute("CREATE TABLE " + table + " (" + + "id UInt32, " + + "name String, " + + "active Bool, " + + "tags Array(String), " + + "scores Map(String, Int32)" + + ") ENGINE = MergeTree ORDER BY id").get(); + try { + client.execute("INSERT INTO " + table + " VALUES " + buildValuesClause(expected)).get(); + + String sql = "SELECT id, name, active, tags, scores FROM " + table + " ORDER BY id"; + + try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { + + for (Map exp : expected) { + Assert.assertTrue(reader.hasNext()); + Assert.assertNotNull(reader.next()); + + Assert.assertEquals(reader.getInteger("id"), ((Number) exp.get("id")).intValue()); + Assert.assertEquals(reader.getString("name"), exp.get("name")); + Assert.assertEquals(reader.getBoolean("active"), exp.get("active")); + Assert.assertEquals(reader.getList("tags"), exp.get("tags")); + + Map actualScores = reader.readValue("scores"); + @SuppressWarnings("unchecked") + Map expectedScores = (Map) exp.get("scores"); + Assert.assertNotNull(actualScores); + Assert.assertEquals(actualScores.size(), expectedScores.size()); + for (Map.Entry e : expectedScores.entrySet()) { + Assert.assertEquals(((Number) actualScores.get(e.getKey())).intValue(), + e.getValue().intValue()); + } + } + + Assert.assertNull(reader.next()); + } + } finally { + client.execute("DROP TABLE IF EXISTS " + table).get(); + } + } - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { + private static Map row(int id, String name, boolean active, + List tags, Map scores) { + Map r = new java.util.LinkedHashMap<>(); + r.put("id", id); + r.put("name", name); + r.put("active", active); + r.put("tags", tags); + r.put("scores", scores); + return r; + } - // First row - Assert.assertTrue(reader.hasNext()); - Map row1 = reader.next(); - Assert.assertNotNull(row1); - Assert.assertEquals(reader.getInteger("id"), 1); - Assert.assertEquals(reader.getString("name"), "test"); - Assert.assertEquals(reader.getBoolean("active"), true); - - // Second row - Assert.assertTrue(reader.hasNext()); - Map row2 = reader.next(); - Assert.assertNotNull(row2); - Assert.assertEquals(reader.getInteger("id"), 2); - Assert.assertEquals(reader.getString("name"), "clickhouse"); - Assert.assertEquals(reader.getBoolean("active"), false); - - // No more rows - Assert.assertNull(reader.next()); + private static Map mapOf(Object... pairs) { + Map m = new java.util.LinkedHashMap<>(); + for (int i = 0; i < pairs.length; i += 2) { + m.put((String) pairs[i], (Integer) pairs[i + 1]); + } + return m; + } + + private static String buildValuesClause(List> rows) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < rows.size(); i++) { + if (i > 0) { + sb.append(", "); + } + Map r = rows.get(i); + sb.append('(') + .append(r.get("id")).append(", ") + .append('\'').append(r.get("name")).append("', ") + .append(r.get("active")).append(", "); + + @SuppressWarnings("unchecked") + List tags = (List) r.get("tags"); + sb.append('['); + for (int t = 0; t < tags.size(); t++) { + if (t > 0) sb.append(", "); + sb.append('\'').append(tags.get(t)).append('\''); + } + sb.append("], {"); + + @SuppressWarnings("unchecked") + Map scores = (Map) r.get("scores"); + int s = 0; + for (Map.Entry e : scores.entrySet()) { + if (s++ > 0) sb.append(", "); + sb.append('\'').append(e.getKey()).append("': ").append(e.getValue()); + } + sb.append("})"); } + return sb.toString(); } @Test(groups = {"integration"}) From 2dc0c7c11737d04df88f118fa6c72f00d9c452b0 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 20 May 2026 12:45:41 -0700 Subject: [PATCH 15/26] Complete client-v2 example --- .../{internal => }/GsonJsonParserFactory.java | 4 +- .../JacksonJsonParserFactory.java | 4 +- .../GsonJSONEachRowFormatReaderTests.java | 1 - .../JacksonJSONEachRowFormatReaderTests.java | 1 - .../internal/JacksonJsonParserTest.java | 1 + docs/client-v2-json-support.md | 24 +- examples/client-v2-json-processors/README.md | 75 +++-- .../build.gradle.kts | 2 +- .../ClientV2JsonProcessorsExample.java | 318 ++++++++---------- .../.gradle/8.8/checksums/checksums.lock | Bin 0 -> 17 bytes .../.gradle/8.8/checksums/md5-checksums.bin | Bin 0 -> 18897 bytes .../.gradle/8.8/checksums/sha1-checksums.bin | Bin 0 -> 19091 bytes .../8.8/dependencies-accessors/gc.properties | 0 .../8.8/executionHistory/executionHistory.bin | Bin 0 -> 45038 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .../.gradle/8.8/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/8.8/fileHashes/fileHashes.bin | Bin 0 -> 19997 bytes .../.gradle/8.8/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.8/fileHashes/resourceHashesCache.bin | Bin 0 -> 18939 bytes .../.gradle/8.8/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .../buildOutputCleanup/cache.properties | 2 + .../buildOutputCleanup/outputFiles.bin | Bin 0 -> 19037 bytes .../.gradle/file-system.probe | Bin 0 -> 8 bytes .../.gradle/vcs-1/gc.properties | 0 .../jdbc-dispatcher-demo-1.0.0-SNAPSHOT.tar | Bin 0 -> 171008 bytes .../jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip | Bin 0 -> 143537 bytes .../resources/main/simplelogger.properties | 18 + .../build/scripts/jdbc-dispatcher-demo | 248 ++++++++++++++ .../build/scripts/jdbc-dispatcher-demo.bat | 92 +++++ .../stash-dir/DispatcherDemo.class.uniqueId5 | Bin 0 -> 10873 bytes ...cherService$DriversHandler.class.uniqueId1 | Bin 0 -> 3465 bytes ...tcherService$HealthHandler.class.uniqueId0 | Bin 0 -> 1494 bytes ...atcherService$QueryHandler.class.uniqueId3 | Bin 0 -> 4109 bytes ...cherService$VersionHandler.class.uniqueId2 | Bin 0 -> 3552 bytes .../DispatcherService.class.uniqueId4 | Bin 0 -> 10339 bytes .../compileJava/previous-compilation-data.bin | Bin 0 -> 1974 bytes .../build/tmp/jar/MANIFEST.MF | 2 + .../com/clickhouse/jdbc/StatementTest.java | 4 +- 39 files changed, 572 insertions(+), 224 deletions(-) rename client-v2/src/main/java/com/clickhouse/client/api/data_formats/{internal => }/GsonJsonParserFactory.java (90%) rename client-v2/src/main/java/com/clickhouse/client/api/data_formats/{internal => }/JacksonJsonParserFactory.java (89%) create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/sha1-checksums.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.lock create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileChanges/last-build.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/fileHashes.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/fileHashes.lock create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/resourceHashesCache.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties create mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/cache.properties create mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/outputFiles.bin create mode 100644 examples/jdbc-dispatcher-demo/.gradle/file-system.probe create mode 100644 examples/jdbc-dispatcher-demo/.gradle/vcs-1/gc.properties create mode 100644 examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.tar create mode 100644 examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip create mode 100644 examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties create mode 100755 examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo create mode 100644 examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$QueryHandler.class.uniqueId3 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin create mode 100644 examples/jdbc-dispatcher-demo/build/tmp/jar/MANIFEST.MF diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java similarity index 90% rename from client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java rename to client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java index adf0a7b48..2444fffed 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/GsonJsonParserFactory.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java @@ -1,7 +1,5 @@ -package com.clickhouse.client.api.data_formats.internal; +package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.JsonParser; -import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java similarity index 89% rename from client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java rename to client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java index 697d5b728..fd797e1ba 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserFactory.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java @@ -1,7 +1,5 @@ -package com.clickhouse.client.api.data_formats.internal; +package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.JsonParser; -import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java index 769ee7b17..bcebe96da 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java @@ -1,6 +1,5 @@ package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory; import com.clickhouse.client.api.insert.InsertResponse; import com.clickhouse.client.api.insert.InsertSettings; import com.clickhouse.client.api.query.QueryResponse; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java index 47e74d44f..85d41ff54 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java @@ -1,6 +1,5 @@ package com.clickhouse.client.api.data_formats; -import com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory; import com.clickhouse.client.api.query.QueryResponse; import org.testng.annotations.Test; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java index 0ee73d2a4..1ad5778bc 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/JacksonJsonParserTest.java @@ -1,5 +1,6 @@ package com.clickhouse.client.api.data_formats.internal; +import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory; import com.clickhouse.client.api.data_formats.JsonParser; import com.fasterxml.jackson.core.JsonParseException; import org.testng.Assert; diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index f61c5aa3a..5bd859b6a 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -157,14 +157,20 @@ unavailable, the factory throws a `RuntimeException` whose message identifies the missing dependency. The shipped implementations construct their own `ObjectMapper` and `Gson` -instances with default settings. Injecting a pre-configured parser, custom -Jackson modules, or Gson `TypeAdapter`s is not part of the public API in the -current release: neither parser exposes a constructor that accepts a -configured library instance, the `JsonParser` SPI itself resides in an -`internal` package and is not a stable extension point, and -`JsonParserFactory` does not accept caller-supplied implementation classes. -Customization of JSON binding should therefore be performed on the caller -side, after the row has been materialized as `Map`. +instances with default settings. To customize the underlying library +(Jackson modules, feature flags, Gson `TypeAdapter`s, number policies, etc.) +extend the corresponding factory and override its protected hook: + +- `JacksonJsonParserFactory` exposes `protected ObjectMapper createMapper()`. + Override it to return a fully configured `ObjectMapper`; the factory uses + the returned instance for all subsequent row parsing. +- `GsonJsonParserFactory` exposes `protected void customize(GsonBuilder + builder)`. Override it to configure the `GsonBuilder` before the factory + applies `setLenient()` and calls `build()`. + +Customization that does not need to influence the underlying parser can +still be performed on the caller side, after the row has been materialized +as `Map`. ### `JSON_PROCESSOR` configuration property @@ -202,7 +208,7 @@ When the configured processor is not present on the classpath at the time a `RuntimeException` with the following message: ```text -JSON processor class not found: com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory. +JSON processor class not found: com.clickhouse.client.api.data_formats.JacksonJsonParserFactory. Make sure you have the required library (Jackson or Gson) on your classpath. ``` diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index e3ec8f646..5a4fb77d1 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -2,12 +2,40 @@ ## Overview -This standalone example shows how to configure `client-v2` to read `JSONEachRow` -responses with both supported JSON processors using one shared table and one -shared dataset: - -- `JACKSON` -- `GSON` +This standalone example shows how to consume a `JSONEachRow` response with the +`client-v2` `JSONEachRowFormatReader` and a `JsonParserFactory`. The two +factories shipped with the client are the customization points: + +- `JacksonJsonParserFactory` exposes a `protected ObjectMapper createMapper()` + hook — override it to return a fully configured `ObjectMapper` (modules, + feature flags, custom deserializers, etc.). +- `GsonJsonParserFactory` exposes a `protected void customize(GsonBuilder)` + hook — override it to configure the `GsonBuilder` (number policy, type + adapters, etc.). The factory still applies `setLenient()` on its own + afterwards, which is required for the stream-of-objects shape of + `JSONEachRow`. + +The example is structured as a small component: + +- `ClientV2JsonProcessorsExample(Client client)` holds the shared `Client` and + exposes regular instance methods (`recreateTable()`, `loadSampleData()`, + `readAll(label, factory)`, `run()`), so the class can be copied as-is into + another project and have its individual methods invoked. +- Sample rows are kept in a plain `Object[][]` constant, separate from the + SQL, so the read path stays focused on the parser factory. +- Two small subclasses, `CustomJacksonParserFactory` and + `CustomGsonParserFactory`, demonstrate the protected-hook customization. + +Each read call in `run()` follows the same three-step shape: + +1. **Create the factory** — `new JacksonJsonParserFactory()` / + `new GsonJsonParserFactory()` for defaults, or an instance of a custom + subclass. +2. **Customize if needed** — only inside the subclass, by overriding the + protected hook. +3. **Execute** — `readAll(label, factory)` runs the `SELECT` and feeds the + response stream through + `new JSONEachRowFormatReader(factory.createJsonParser(...))`. ## Requirements @@ -25,10 +53,10 @@ gradle run Connection properties can be supplied as system properties: -- `-DchEndpoint` - Endpoint to connect to (default: `http://localhost:8123`) -- `-DchUser` - ClickHouse user name (default: `default`) -- `-DchPassword` - ClickHouse user password (default: empty) -- `-DchDatabase` - ClickHouse database name (default: `default`) +- `-DchEndpoint` — endpoint to connect to (default: `http://localhost:8123`) +- `-DchUser` — ClickHouse user name (default: `default`) +- `-DchPassword` — ClickHouse user password (default: empty) +- `-DchDatabase` — ClickHouse database name (default: `default`) Example with custom connection properties: @@ -44,15 +72,22 @@ gradle run \ `com.clickhouse.examples.client_v2.json_processors.ClientV2JsonProcessorsExample` -- Runs the following steps in order: - 1. defines table `client_v2_json_processors_example` with primitive columns - and one `payload JSON` column; - 2. loads sample rows from `src/main/resources/sample_data.csv` into that table; - 3. reads the same rows with `runGsonExample(...)`; - 4. reads the same rows again with `runJacksonExample(...)`. -- Reads rows back through `client.newTextFormatReader(response)` and logs the - primitive columns together with the parsed JSON object from `payload`. +Steps performed by `run()`: + +1. `recreateTable()` — drops and re-creates `client_v2_json_processors_example` + with primitive columns and one `payload JSON` column. +2. `loadSampleData()` — inserts the rows from the `SAMPLE_ROWS` array as a + single batched `INSERT`. +3. `readAll(...)` is invoked four times, each time with a different + `JsonParserFactory`: + - default `JacksonJsonParserFactory`; + - `CustomJacksonParserFactory`, which overrides `createMapper()` to + tolerate unknown properties and preserve big integers and decimals + exactly; + - default `GsonJsonParserFactory`; + - `CustomGsonParserFactory`, which overrides `customize(GsonBuilder)` to + use a `LONG_OR_DOUBLE` number policy and disable HTML escaping. The build keeps both `jackson-databind` and `gson` on the classpath so the -example can switch between processors at runtime. Production applications only -need to keep the processor they actually use. +example can switch between processors at runtime. Production applications +only need to keep the processor they actually use. diff --git a/examples/client-v2-json-processors/build.gradle.kts b/examples/client-v2-json-processors/build.gradle.kts index 59ec03181..e9f39b34e 100644 --- a/examples/client-v2-json-processors/build.gradle.kts +++ b/examples/client-v2-json-processors/build.gradle.kts @@ -29,7 +29,7 @@ application { } tasks.named("run") { - listOf("chEndpoint", "chUser", "chPassword", "chDatabase", "jsonProcessor").forEach { key -> + listOf("chEndpoint", "chUser", "chPassword", "chDatabase").forEach { key -> System.getProperty(key)?.let { value -> systemProperty(key, value) } diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index abb6b14f3..bae569319 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -1,240 +1,190 @@ package com.clickhouse.examples.client_v2.json_processors; import com.clickhouse.client.api.Client; -import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; +import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; +import com.clickhouse.client.api.data_formats.JsonParserFactory; +import com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory; +import com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.data.ClickHouseFormat; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import java.util.Map; +/** + * Demonstrates how to consume a {@code JSONEachRow} response with the client-v2 + * {@link JSONEachRowFormatReader} and a {@link JsonParserFactory}. + * + *

The class is intentionally written as a regular component (instance methods, + * shared {@link Client} field) so it can be copied as-is into other projects and + * have its individual methods invoked.

+ * + *

Two factories ship with the client and serve as the customization points: + * {@link com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory} and {@link com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory}. Extend + * either of them and override the protected hook + * ({@code createMapper()} for Jackson, {@code customize(GsonBuilder)} for Gson) + * to plug in any library-level customization.

+ */ public class ClientV2JsonProcessorsExample { + private static final Logger LOG = LoggerFactory.getLogger(ClientV2JsonProcessorsExample.class); - private static final String TABLE_NAME = "client_v2_json_processors_example"; - private static final String SAMPLE_DATA_RESOURCE = "/sample_data.csv"; - private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" - + "id UInt32, " - + "name String, " - + "active Bool, " - + "score Float64, " - + "payload JSON" - + ") ENGINE = MergeTree ORDER BY id"; - private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " - + "FROM " + TABLE_NAME + " ORDER BY id"; - public static void main(String[] args) throws Exception { - ConnectionConfig config = ConnectionConfig.load(); - defineTableStructure(config); - loadData(config); + private static final String TABLE = "client_v2_json_processors_example"; - runGsonExample(config); - runJacksonExample(config); - } + /** Sample dataset: {@code { id, name, active, score, payload }}. */ + private static final Object[][] SAMPLE_ROWS = { + { 1, "alpha", true, 1.5, "{\"city\":\"Berlin\",\"tags\":[\"a\",\"b\"]}" }, + { 2, "beta", false, 2.5, "{\"city\":\"Paris\", \"tags\":[\"c\"]}" }, + { 3, "gamma", true, 3.5, "{\"city\":\"Tokyo\", \"tags\":[]}" }, + }; - private static void defineTableStructure(ConnectionConfig config) throws Exception { - LOG.info("Step 1. Defining table structure: {}", TABLE_NAME); - try (Client client = createClient(config, "GSON")) { - executeStatement(client, "DROP TABLE IF EXISTS " + TABLE_NAME); - executeStatement(client, CREATE_TABLE_SQL); - } - } + private final Client client; - private static void loadData(ConnectionConfig config) throws Exception { - List rows = readSampleRows(); - LOG.info("Step 2. Loading {} sample rows from {} into {}", rows.size(), SAMPLE_DATA_RESOURCE, TABLE_NAME); - try (Client client = createClient(config, "GSON")) { - executeStatement(client, "TRUNCATE TABLE " + TABLE_NAME); - executeStatement(client, buildInsertSql(rows)); - } + public ClientV2JsonProcessorsExample(Client client) { + this.client = client; } - private static void runJacksonExample(ConnectionConfig config) throws Exception { - LOG.info("Step 4. Running client-v2 example with Jackson"); - try (Client client = createClient(config, "JACKSON")) { - readRows(client, "JACKSON"); + public static void main(String[] args) throws Exception { + try (Client client = buildClient()) { + new ClientV2JsonProcessorsExample(client).run(); } } - private static void runGsonExample(ConnectionConfig config) throws Exception { - LOG.info("Step 3. Running client-v2 example with Gson"); - try (Client client = createClient(config, "GSON")) { - readRows(client, "GSON"); - } + /** Runs the full demo: prepares the table, loads sample rows, reads them four times. */ + public void run() throws Exception { + recreateTable(); + loadSampleData(); + + // 1. Default Jackson: use the shipped factory as-is. + readAll("Jackson (default)", new JacksonJsonParserFactory()); + + // 2. Customized Jackson: extend the factory and override createMapper(). + readAll("Jackson (custom)", new CustomJacksonParserFactory()); + + // 3. Default Gson: use the shipped factory as-is. + readAll("Gson (default)", new GsonJsonParserFactory()); + + // 4. Customized Gson: extend the factory and override customize(GsonBuilder). + readAll("Gson (custom)", new CustomGsonParserFactory()); } - private static void readRows(Client client, String processor) throws Exception { - try (QueryResponse response = client.query(SELECT_DATA_SQL, new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)).get()) { - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + /** + * Reads every row from {@link #TABLE} using a {@code JSONEachRow} stream + * decoded with the supplied {@link JsonParserFactory}. + */ + public void readAll(String label, JsonParserFactory factory) throws Exception { + LOG.info("--- Reading rows with {} ---", label); + + QuerySettings settings = new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow); + String sql = "SELECT id, name, active, score, payload FROM " + TABLE + " ORDER BY id"; + + try (QueryResponse response = client.query(sql, settings).get(); + ClickHouseTextFormatReader reader = new JSONEachRowFormatReader( + factory.createJsonParser(response.getInputStream()))) { + while (reader.next() != null) { Map payload = reader.readValue("payload"); - LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}({})", - processor, + LOG.info(" id={}, name={}, active={}, score={}, payload={}", reader.getInteger("id"), reader.getString("name"), reader.getBoolean("active"), reader.getDouble("score"), - payload.getClass().getName(), payload); } } } - private static Client createClient(ConnectionConfig config, String processor) { - return new Client.Builder() - .addEndpoint(config.endpoint) - .setUsername(config.user) - .setPassword(config.password) - .setDefaultDatabase(config.database) - .serverSetting("allow_experimental_json_type", "1") - .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), processor) - .build(); - } - - private static void executeStatement(Client client, String sql) throws Exception { - try (CommandResponse ignored = client.execute(sql).get()) { - LOG.debug("Executed SQL: {}", sql); - } + public void recreateTable() throws Exception { + execute("DROP TABLE IF EXISTS " + TABLE); + execute("CREATE TABLE " + TABLE + " (" + + "id UInt32, name String, active Bool, score Float64, payload JSON" + + ") ENGINE = MergeTree ORDER BY id"); } - private static List readSampleRows() throws IOException { - InputStream stream = ClientV2JsonProcessorsExample.class.getResourceAsStream(SAMPLE_DATA_RESOURCE); - if (stream == null) { - throw new IOException("Resource not found: " + SAMPLE_DATA_RESOURCE); - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { - String header = reader.readLine(); - if (header == null) { - throw new IOException("CSV resource is empty: " + SAMPLE_DATA_RESOURCE); - } - - List rows = new ArrayList<>(); - String line; - while ((line = reader.readLine()) != null) { - if (line.trim().isEmpty()) { - continue; - } - - List values = parseCsvLine(line); - if (values.size() != 5) { - throw new IOException("Expected 5 columns in sample CSV but found " + values.size() + ": " + line); - } - - rows.add(new SampleRow( - Integer.parseInt(values.get(0)), - values.get(1), - Boolean.parseBoolean(values.get(2)), - Double.parseDouble(values.get(3)), - values.get(4))); - } - - return rows; + /** Inserts {@link #SAMPLE_ROWS} into {@link #TABLE} as a single batched INSERT. */ + public void loadSampleData() throws Exception { + StringBuilder sql = new StringBuilder("INSERT INTO ").append(TABLE) + .append(" (id, name, active, score, payload) VALUES"); + for (int i = 0; i < SAMPLE_ROWS.length; i++) { + Object[] row = SAMPLE_ROWS[i]; + sql.append(i == 0 ? " " : ", ") + .append('(').append(row[0]) + .append(", ").append(sqlString((String) row[1])) + .append(", ").append(row[2]) + .append(", ").append(row[3]) + .append(", ").append(sqlString((String) row[4])) + .append(')'); } + execute(sql.toString()); } - private static List parseCsvLine(String line) { - List values = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean inQuotes = false; - - for (int i = 0; i < line.length(); i++) { - char ch = line.charAt(i); - if (ch == '"') { - if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { - current.append('"'); - i++; - } else { - inQuotes = !inQuotes; - } - } else if (ch == ',' && !inQuotes) { - values.add(current.toString()); - current.setLength(0); - } else { - current.append(ch); - } + private void execute(String sql) throws Exception { + try (CommandResponse ignored = client.execute(sql).get()) { + LOG.debug("Executed: {}", sql); } - - values.add(current.toString()); - return values; } - private static String buildInsertSql(List rows) { - if (rows.isEmpty()) { - throw new IllegalArgumentException("Sample CSV does not contain any rows"); - } - - StringBuilder sql = new StringBuilder("INSERT INTO ") - .append(TABLE_NAME) - .append(" (id, name, active, score, payload) VALUES "); - - for (SampleRow row : rows) { - sql.append('(') - .append(row.id) - .append(", ") - .append(quoteSqlString(row.name)) - .append(", ") - .append(row.active) - .append(", ") - .append(row.score) - .append(", ") - .append(quoteSqlString(row.payload)) - .append("), "); - } - - sql.setLength(sql.length() - 2); - return sql.toString(); + private static Client buildClient() { + return new Client.Builder() + .addEndpoint(System.getProperty("chEndpoint", "http://localhost:8123")) + .setUsername(System.getProperty("chUser", "default")) + .setPassword(System.getProperty("chPassword", "")) + .setDefaultDatabase(System.getProperty("chDatabase", "default")) + .serverSetting("allow_experimental_json_type", "1") + .build(); } - private static String quoteSqlString(String value) { + private static String sqlString(String value) { return "'" + value.replace("'", "''") + "'"; } - private static final class SampleRow { - private final int id; - private final String name; - private final boolean active; - private final double score; - private final String payload; - - private SampleRow(int id, String name, boolean active, double score, String payload) { - this.id = id; - this.name = name; - this.active = active; - this.score = score; - this.payload = payload; + // --------------------------------------------------------------------- + // Customized factories + // --------------------------------------------------------------------- + + /** + * Customized {@link com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory}. Override {@code createMapper()} + * to return any {@link ObjectMapper} you want — modules, feature flags, + * deserializers, etc. all carry over to row parsing. + * + *

This example tolerates new server-side keys and preserves big integers + * and decimals exactly inside the {@code payload} JSON column.

+ */ + public static final class CustomJacksonParserFactory extends com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory { + @Override + protected ObjectMapper createMapper() { + return JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true) + .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) + .build(); } } - private static final class ConnectionConfig { - private final String endpoint; - private final String user; - private final String password; - private final String database; - - private ConnectionConfig(String endpoint, String user, String password, String database) { - this.endpoint = endpoint; - this.user = user; - this.password = password; - this.database = database; - } - - private static ConnectionConfig load() { - return new ConnectionConfig( - System.getProperty("chEndpoint", "http://localhost:8123"), - System.getProperty("chUser", "default"), - System.getProperty("chPassword", ""), - System.getProperty("chDatabase", "default")); + /** + * Customized {@link GsonJsonParserFactory}. Override + * {@code customize(GsonBuilder)} and configure the builder; the factory + * applies {@code setLenient()} on its own afterward (which is required for + * the stream-of-objects shape of {@code JSONEachRow}). + * + *

This example parses integer-shaped JSON numbers as {@code Long} (the + * default is {@code Double}, which loses precision for large {@code Int64} + * values) and disables HTML escaping on round-trips.

+ */ + public static final class CustomGsonParserFactory extends GsonJsonParserFactory { + @Override + protected void customize(GsonBuilder builder) { + builder.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .disableHtmlEscaping(); } } } diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock b/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..35e148177bf2a0218ce98a957287f455a676e5b0 GIT binary patch literal 17 UcmZRcQT}jGUfG_L3=p6R06${|LI3~& literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin b/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..5ee85113546b3e11b96654bff97b50e7762450bc GIT binary patch literal 18897 zcmeI(?@JSL0LSs0u%R|=62`!Fr%QuqbsC z#9$E74;3^F%x8@VZO{W{f(;_72PK5Y@Ih1%*`43}BDw!S-UoO0<#XT9-F;tAkMk&s z-L`3L%J!DGJ!FIc0tg_000IagfB*srAb9F?e_wIb2F%9%q|8U-_oiam zu0zKB2QtsC%Zz;PZ+U9ul`^ki`g)^#z#1~{b5-V-mrtkuq^s?)$H#l|u^oxd?l4GK= z+#nid<>`&prdUDye&5kDhtCsl{B>S5ez%Io9{o3m9dGq#EjZlM;atDd*mGgg>YUqZ zMBFyfFzbum(Fk`H`#s%-)xOxx&YXDX53LbZzfGr%XB4J496v*CUsgQx&HW?(wp8yv m(Rk(+jq<V+Xj5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SNpr0!8$L1d@jl z#Vuh5D|8g42%-nV^L^row}%Qw$lV$&O(1ScXPSB-})|kVs8`4vz=W2 zwc>M6#H{QRo2$4yps9Lj+SThKc}^*phaW2)tM$3v$ev%v<#J=$ExXOnmpxy`<*Jtx z)!O=?vn0>bak)ChH>18Ke~i7(6qg?v+$1%1haO~eF_%{w#6CB#j?3BWq;om9rzjsW zIfg9+AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<;NKKTq^Gr!USpUw%q)4+@V(#Y^jl?`xw5iKT&r{n=wHV-OQ(jr z$o!XXq4x=Yw|4K_erJtyz*BQkx!lkerHwnhtBh{xQ}sPo(u%ytZ;k#;6#Sa|6ldD-uDI~2~HE9W%zdbCPVcJvA zV@c|7??MLk!WIu{h9%Z;JL*}yHP!HW|mVPv8 znU=5STpYi(>|tf2J+I;9{PB`DMR8=;_0!j6q}5KhnMD{yZx+pP`b*z8Q$lM}XJ3iq zNvf&n#QXIhNUI~;TcVQZ`K0+t0RI$i4j4{MP8^%T*qIMQ(Yy-7PZi57x;w^!s7%W!vg) J31iGm>kkn3ICB61 literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties b/examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties new file mode 100644 index 000000000..e69de29bb diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin b/examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3eb3dbb161fd0890222768304f323159e36ff61 GIT binary patch literal 45038 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 V#EzD7$APaeJ6B)j|Kwm&egp45Bdq`c literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties b/examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties new file mode 100644 index 000000000..e69de29bb diff --git a/examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..380783ace6c0ebad386d0399f9ef25a22d280b78 GIT binary patch literal 17 UcmZQ(-TdwS~dyF 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�=+ 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�HGYXzg-E^{{i;= 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 literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip b/examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip new file mode 100644 index 0000000000000000000000000000000000000000..af28c826f4673716555cb4b8a3454c8524a0cf96 GIT binary patch literal 143537 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ĭ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%0D*8(M-L&hNF>}%V9v;epzp<57GIL+ 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 B#GU{E literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties b/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties new file mode 100644 index 000000000..1f90e6a60 --- /dev/null +++ b/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties @@ -0,0 +1,18 @@ +# SLF4J SimpleLogger configuration + +# Default log level for all loggers +org.slf4j.simpleLogger.defaultLogLevel=info + +# Log level for specific loggers +org.slf4j.simpleLogger.log.com.clickhouse.examples.dispatcher=info +org.slf4j.simpleLogger.log.com.clickhouse.jdbc.dispatcher=debug + +# Show date/time in log output +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss.SSS + +# Show logger name (short form) +org.slf4j.simpleLogger.showShortLogName=true + +# Show thread name +org.slf4j.simpleLogger.showThreadName=false diff --git a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo new file mode 100755 index 000000000..2c8571ca6 --- /dev/null +++ b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# jdbc-dispatcher-demo start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh jdbc-dispatcher-demo +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and JDBC_DISPATCHER_DEMO_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}.." > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/lib/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.jar:$APP_HOME/lib/jdbc-dispatcher-0.9.6-SNAPSHOT.jar:$APP_HOME/lib/slf4j-simple-2.0.16.jar:$APP_HOME/lib/slf4j-api-2.0.16.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and JDBC_DISPATCHER_DEMO_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and JDBC_DISPATCHER_DEMO_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + -classpath "$CLASSPATH" \ + com.clickhouse.examples.dispatcher.DispatcherDemo \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $JDBC_DISPATCHER_DEMO_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat new file mode 100644 index 000000000..50e608383 --- /dev/null +++ b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem jdbc-dispatcher-demo startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%.. + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and JDBC_DISPATCHER_DEMO_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\lib\jdbc-dispatcher-demo-1.0.0-SNAPSHOT.jar;%APP_HOME%\lib\jdbc-dispatcher-0.9.6-SNAPSHOT.jar;%APP_HOME%\lib\slf4j-simple-2.0.16.jar;%APP_HOME%\lib\slf4j-api-2.0.16.jar + + +@rem Execute jdbc-dispatcher-demo +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %JDBC_DISPATCHER_DEMO_OPTS% -classpath "%CLASSPATH%" com.clickhouse.examples.dispatcher.DispatcherDemo %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable JDBC_DISPATCHER_DEMO_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%JDBC_DISPATCHER_DEMO_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 new file mode 100644 index 0000000000000000000000000000000000000000..9f669e95ceecd1d93169d3f64784e01243ea145f GIT binary patch literal 10873 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 literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 new file mode 100644 index 0000000000000000000000000000000000000000..c933b93c89cef9e9f68ee8da8e7cc137f4544f80 GIT binary patch literal 3465 zcmbVO>30)V6#u<8?WE~Y(g4AtU@Zl@1OkdE4OF2N+S)})Ev8s?nmn3m)0r?cDVzJg z;f^9MxPluB9texVaXY9SJx9NK{NUf}@niR|FNiunJ-@E(o-uo_GJaZ1f zT6|f80u)Mc$S6WFLsM8E_J?C?cqpnTOz!79mEm}doBoJu#uY0Z<%WO5*fPkC5jD)K zHyG*&H_R4Ai^RCWP;5n2vwAths`lx~3%KA!i3FF7QpgPML1jeo#}sYA-_<|J!xm{+ zqiU+v$WT~Q+v7wTEjBfGyWmE-1dohan9Z?H?UXxY3o93_FA zqz`awGvA)zrq#i%s2*XMT~pgWt}|#Esy0AIc`;YQ6*4L?Z-PwK=|yNhu+p>Sg$_l)*KQ9flRT zh!~9YhbKpujVg+}AIl}IkZ~=pWANvMXDWV%a!POy!Bf?>4n4SPc~hTRT6HHaU)hUG)!L(x{ej3xjV8_a}tVi4KC0Pe7PCsj3A_wTv+E{54CTU<=W@R&G3 zOp6l@LYEQ8cJgh|)U`|(TvD?&FFJuq^$@aHpFnLw#t3%UU9Xexpr$uw^YW={VnQZU z9dVa%a5ux^?9^piUcPZo+$#X3Yh0lEei;u4haIN6n|o*=JtX5{JVO0vSQ+9?x)xTf zEvglzm1!xOMP97V){}-vzM9E`Eu#3CjK@Wfb&6)$Mv;{8Btwj3eBi=|I3(e)K=Vfo z)#K)=+K4{H{WfrIfNxa7)Vp^v?9YaF`k*b;2BBHquIodIcpxuS*^~*4-MivEdox`y z*&q!IY=JYJwZl2srP2Wg=C zol&2}%p~RXcRlgQJ2KBBvzhA16>##6^_%m znc;QlVzrUs8?-eP%|8H_&(oen=a#}opWt&l&%zrj^lT1>92G@LbO%F4o-M&pv3Loc z!kxj8AZG+;k*I7n6*#rgp8zCm_wLh-zd z;?BL0Nm%zUDkZf2gYHpcEs{if-S$=+Xx}?Z(sRgk#7keiNx~eo{T4kB(z*ce;N8^! f0=!560+R33?_+#QZywq&peghjt&iXf0$jy^)yCl$ literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 new file mode 100644 index 0000000000000000000000000000000000000000..93ba4967870dbf9ae0f3d48270513b78f285a041 GIT binary patch literal 1494 zcmbVMTTc@~6#j-5b}5U%a#2ySa*>wXq9Te#RIbHvNh~2jpO)=}E-brsXNo+TsEH5y z?2C{70}`!?i9Q>Dl<`c91%grJVRz1)IhXHTfBgLN4ZvN@*P;p$4N(Kts9~6Nf^Ez3 zTxTm66a=^UuDzZ2xUjOW$lKD%@z9#CkQpBCxDM}5bK8@-wC!g-9x~L(oGZGA8AcaQ zV=fhgj#>>d19dPM8aM47+wyFG!&+Y7yRg79db$#r6b4YvPc48FDK5SjR;Mv+N`82QqGZUhs@(W9YzT4RHfk z(8(}znv^lr3hrlT!Z4uxrxK;S|0DF&{2`S4L8@eSQzSy#vLJL^WvB|abR-xwl|3r9 zjy}rJW_H~fJm`tREZ+`PAOi*lF~ktt6oLP{TD^%Ue{gl2r3?%kxTeyet9Txg5e+vO zI{#A2O52TC-3Rf983 zpQ^o&PD_fiO`S-C!;m;x-4tG(R~oqIM8zhRkc)iai&9X>+MA(1BW-7E(a!G&8KW8D zdFhghhAD>Tl8zf#^UIYNDs(0&gbtr|RRC?rR>P1|BHv3iUg<#4=o<6B&%-It7J^d_ z>yPXX#AzMrV7Ly}*r-+#ZK*2y>M%mwn?y~}U2$T4{hz@4K116p$&Av!hGc-}xJCcA zvJ`G(jBX@fvc^c_4pQ`{Y<(bJgw8p$zQ2fuB3j?0sqZ6Le-nFWUMS+yp1NsY(Y6*b zyE1E0vnR7wZT4o?YRqJ&i2lX0G`c4@5&oXE+F7kemb`IfuW^!%E!#S5Z5-RdS@|HA<=BZNACeVvK}NgJ(nzbF z^~|hn5kic)0_HH_KoTGv0YbPFoDDJ%hs&fW{!s;0zz@zJKow=EP)?x0d~art(ShU2 zkDZR+S7ZCTqY}Rfv`JfI;}Sd+|>74taV2b zMS})iM;ooMlx&UMw9zS#%0>2hplGLbhiuCwzUsO(9D-xdxxTk;VKPFbX<)V zfmO=6Q^+NA(oK%JZr)+Uq+kzUdyl2ZXi(DnsB{m?BL(TW{n8z?G6GB5+WV?L2VL9D zjS|!{#5F9}@j9%S1LLi~z zdaM+vcVsTpYugrC>~8bedzApX_Xd2GptcVyCR%l@#u|afYJi6h?j`babcz;A7+D+I zHFW6cRJ7?{=%q!02)BF3t8lK@(S;;!5FcB?HM7ZnBi|jt z2A-62B-?SV^pRRNuIquy@RXU27CK@dxpBYx|GcwI^Ll8 z2=V+Um0+ii9wlgStwZ^|w0jIkM$pTH_xCBCTXpOKNscq9Q7og!7qhA69Vg)NvQ?W{B0U zBCug@rH^Mu(hK*UUAD<8B*)hv;Q(HFNxG|ZLhmAtkRgT}R z<31PyO=&BaV;eB7+~4UGaSanXvY6yyc?myTM+Ig}!&iMM|a@2p|CUpmfOcpma%^Ktab8juMbv77tmsIaaP}M&YUM zS+HFSOzU`y>S__y)ztn5ycG{u9y<2OJ^-dYIu8@C9m-hjoH>wV_bZy(}1GWk+}Wu;PowU@GVDWj$8;I?%_PK;~60MR1a} z?e(;TR4rIl&j)mT5Fa8cPa@C}bTPkBf=;7&2p`t*5!EuHY-s&N$%Yf-g?(Db!}yrM z(n(`NGE35it2zbW*fw%5k91b=E4Pe5MV3Sw9uZi1nR~8sC*SPfCe>2Avgv^2u}Sn; z8QzpleP&J$6edTcJ*1Ap>qRWqVZ%1ncS#shx4%Ho+>UTX)5xqzf(<>)Tbe_Kmw1;6 zvwWRC{u8k_Kz_+-w`V}|OuldQYIYNe!zCZzfS^QXEuP~rlD z9+y_v1_Q~(%@J9dIaXiM&Cck(+lpwLe5>UflGa-KRG+L+Kb-_?{|@=LK4;hi2xctO?21r)Ds}Qg$0@*RrPt9tqZB zNzpgzXT8(fXIT@4eE0l-1Cm)Ahsfgs>URh97Z^lJCVDu~4Vtdx)%JqGqCh7}B?KGt zUfx=EPu5_w;*sj!dZ|7&{77K+ys#IFM#Fi5YcH?L%<)4& zjd+yO$N09L-%1kg=zJEn9nYcuX;0@9e1@q5{1Tt!v&GlKr|>wxsP9>0(&N+k4A*F@ zHbrmsfG}cBokc7zV(DX8+;IlAor}f8v1^LB?rHC z^N6l5V#}kIJ*Q$@)gB7B7ja8$R}s6b22aIqQ;W}_FSdXE3=Y;p**=3e#_sW7-c-a$ zDijaRfHPRCE?!5iTn$oTwHyx@;ifbX&SJb6*NQk651&UzJUoN@1)fAAr71y6MWCl5 zaV;Jx;zV3K7q3&gbu)OkuNzi^mWn`Eo0ZWM@$k8LNa+&CdwktGC1|M#^b|KMJSXCH z=bprxR7l12KDGXSKPb}oQan_|sk8VfprR)EkMrfnybXVbpW_$&KaJnx@BDummqZQ! zPl`q28XOm`qEmG7drG9lHr`1gsH3&9g3n(;$FC8`Z?GJ{MLT|n&G-Yy-an!rf8x~n zXWWawU>twN1pdaE-~vw4$7%e7-e1B~_$M)3B#I04eUU2{#3Edz*9#(rf6?1Tu>&uQ z+wqFH5C8Vk=pH7>Q%t|l;&WaMyYK`)&qO=TQQ-^tBI@Yj8)W}W2yyTA)crCMGo{Lj z$G5NGt4zCgu?1hl*O_{o#X4-Dy;|-BBPy?blWQuOpFnK+f3RA^Mh(jvS6srK&@_ZJ z#C(wkQ-h^(I1w6-UqUk?3odif(}=u+nt_t$f`;a+h36Tp>g?3;&`LZ*pdmv47N5`Z ky@u32=lu(OoBt86&G2^)-{)71>q`3veE%U2;3tUx51D#~QUCw| literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 new file mode 100644 index 0000000000000000000000000000000000000000..5b3fd2b3d11d1274fcbc70362efff957fed76286 GIT binary patch literal 3552 zcmbVP+jARN8UGzw(kk*Mw$;Rm(p*I2G`3?~O`DWf%7w;pY8TmQBIDwgV%Do;Yi)T~ z*^5;WaOPDg2IBlI06&rVrXZ z=R4o|uD|d5R)72G{XYXZfp;{71df`{LfR}@=6unqcrq;)jfHYadg+4Yl?~r4N;iF` zvCK>Nl4Z&*2qU5)szXCeVDOX3=$eqOXF2w;VHZl$6^Qyp%j>#F;Qrhvk(Z4lj#dr2 zj;+`x&^~8eGSVf(o=u;do|C3e4aY6p@=pr1boWfe(9XrfL*sEIaEFHNI(DE_;DA!@ zRqV7a{dCdy%N~7Z5Qe!NS~QCkC@Fnb`WNKIiuC+Z=@*@X!1nH*+^WvJ?^^aOjoO8z zhTS^u#GW-q1q?KbQjC%(u%o-_dSG(4=kXZ!2_ym?DckW=MycdnmW4P3QaV0`y9Bm) z(k=|SuEQv1yQ|Ur7zeT=O}c8P_DpP{%q|@VakoHxHU9!f)@Nj{Fl}y*kTb5uXk}yQ z7U))NnN@IQ(&>_88l|G+`I!ff+YK%422b2JuQ@*tzTX7sGG@R6N3ZEfn8b&lE3+QrDniXH3uSoX_6SRrnO&(s5 z9+Q)oJ{>%bK@Deg4B>3E>;{WK*p`cQinT@14wGp$@l!P~Vt9mMuWgjfoQ_e95y32{ zdO_U%|Bb3vb1gqHJSx!I=|=XZZ%G+8P{2;rzpkN;9gNKPU-lP>PHc?_K$v6xYbV$qyY&8mne5G8bTu-vI>}rlk<3_e;LZy64fp1E~96QKr z%FpN6t7KVKkt(i|js@6EPRW^NW^#@@oAydGN9WQxXLhz)uVsO4XAIw%HawY1#66Xp^P@(&7TkCP!_56l^}4Z~^vJo!2~m`HDy}KdS}Jax8@^QrR7hyf7)SLm z2M>XK+Hri(ca3r|R=u_;ei6s3cum9W3WINO46X*!vM)LFlKoi?^Z=G;4byj21AVy} z_%+5rXn;kpE9W@#m2!4tOKGC4xj25MEH`0q?o;)TgN-5v`E`)De90N|D}j!+^_)ow zPJj_+&>bupY%Rpawuv=gVH$oTaBzd~o4KUnw*vca%}PKA{v*XNM%6^W2GQQGz8YEY zA^x`FX;Poz>LmAy67N0yN3`_*30vOfE`+c186^+!JiftaXTXK4_$GJcuSz2t@hqO> z*)zPf!W$YOP9)k6FQa1_J6}OZ?|W!D+#y~}>|MtGch&Aa97r4r7Cp=8AMIPgeF9_s zi3eAZ4IvXwhF9>Az%`62uJ-5$*frUbcsM`Flf=jhJ{v-QGLkr#pNx`lu13%j=kt@X zL_WWa&t)QkTKqK`S;piN+Jn8gnfL#O)RevCKq z0^TBJiJGrb-x4)m!%Zwv(>0OAJJhfw_7Z;Gcv~FD@2apErnOfIr*Glgh*0_u@Ev@Y zpgV@^_#VDbiw5u?Qh&%lJ$tbqKcXhWO`NC$iL2-F6T;{P+PsL4-5+65!%+>#HJsMR zG#LLyJE9uG8WN2Ik|FqreTY!5%Ck-VGiv5p&#+aUHW-3a?L&yu8m#?{7HG9##KQO) k*CD)2@6}r{a$w;IMo-|Ej1~CLB~;~plk2w_skf2*A4jO*ZU6uP literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 new file mode 100644 index 0000000000000000000000000000000000000000..4a9a971b58a0daa26152f9e6e61ffe9499ce5b72 GIT binary patch literal 10339 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 literal 0 HcmV?d00001 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000000000000000000000000000000000000..b2fd138014ac1ae12ce735d705fc39e53a6da63b GIT binary patch literal 1974 zcmZ`)eNdxL9*Y50(-9O&@yzl#a ze$U7Iyw4j2YE(+nrN#(lBppS+LXV@zgU`&{CqSp!#+mD^yvNN(Lp$5R*qt`kJrO!B zoZHC=W-IHG!Ml^7R;fM#HR)O zVEy6#^_{6Ra`RGs|&qR^0IDJpG#bys2fUYEc6#@!O? zYmaJn4~1q(A1A02mr0Vq?_phyG1Rr)7q`}*nAM^BmAv?=amw$d)B@oS(|`P}&p$Cs z2C{o6)g>zm?Khi7d-I~0m667#U2nv`s!ml;r_<;e^lS9^nc#DK9O>Y5 zu)+_Z(<%r~HwlWw`a1Z?BnBY@V&f7(`kjzbm^usE8q8M4QNtoiUvAG|B%-dz}m@2fD^jccMkR*9ohajth&1|C?{EW@RTENF}E^2V=vqZcxr&(*8m~Z~w zAEJw?0`IkNu3s)r=M(K;-#>lls+OvFFXv|Fsq*QImN3Gjq-PsgQ?;Q+VWl}*Q44KW z*5)Lk$m+mvWzBUqVodP39A;`Tsp8}aE1{M}NLJ4=JgBzo97Zb-9iA$;zzH6KRZ+L| z&RT!Hap79Su8b*9{n8UodA-7=bn6A$Lo2+b1f86WeCr@+VVnZFJ=;?IUO4ZKJeu>E zN#F3Nn^*Ky!7mS9Kf4~;2iG?ru%5oKszF!TNUFfg+Nk>22VDE$>Knt2#{AL?d%tKh zt~V^oFQC?Xr+vl0$T78cjf7?_xZU5pbVK1rov;alq#`&+O$&lw%-6dJK@Vpm2SI7J z8A^kDOeLf9e5rlq@9%9z)n?TDP_tC#M+h*kI@YD5dUJN-;fHa|nPpWMqmNKS0W1un zu5wcgQj)5q0=&=ecId@}r z_VefWCysq9r;e7tQaHpFXhg@Wz%9~Duf8-}Me%viHS6PNf|N8{p;+OVu0jgHA3 z2yHIj%Zbh&dHf_dYGbb?dFt(+%L6x#bRwuw=!N2ZXU_zlcvkk{@?I=` zAJe;0whf`3)C?nD{XPU=qm!ksesw`X<;S60{+N$X`O(p+{WkM(7t2ZKx&g?j>TB+#bT_Rxz-BB0>u*$XE=A$Rtt> zivWo>kedY{`5Dqj&^Uz3qnLFJvxYHi1hd9Zh$e5s@NY$9d` Date: Wed, 20 May 2026 14:39:07 -0700 Subject: [PATCH 16/26] Corrected examples according to the implementation --- examples/client-v2-json-processors/README.md | 19 +- .../ClientV2JsonProcessorsExample.java | 134 +++++- examples/jdbc-v2-json-processors/README.md | 130 +++++- .../jdbc-v2-json-processors/build.gradle.kts | 2 +- .../JdbcV2JsonProcessorsExample.java | 413 +++++++++++------- 5 files changed, 500 insertions(+), 198 deletions(-) diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index 5a4fb77d1..097ea6f47 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -25,6 +25,12 @@ The example is structured as a small component: SQL, so the read path stays focused on the parser factory. - Two small subclasses, `CustomJacksonParserFactory` and `CustomGsonParserFactory`, demonstrate the protected-hook customization. + Both also implement a tiny `PayloadConverter` interface defined inside the + example: their configured `ObjectMapper` / `Gson` is reused to convert the + raw `payload` `Map` produced by the underlying library into a typed + `Payload` POJO. The default factories do not implement the interface, so + `readAll(...)` logs the raw map for them — making the contrast between the + default behavior and the customized behavior visible in the output. Each read call in `run()` follows the same three-step shape: @@ -83,10 +89,19 @@ Steps performed by `run()`: - default `JacksonJsonParserFactory`; - `CustomJacksonParserFactory`, which overrides `createMapper()` to tolerate unknown properties and preserve big integers and decimals - exactly; + exactly, and implements `PayloadConverter` to convert each row's + `payload` `Map` into a `Payload` POJO via + `ObjectMapper.convertValue(...)`; - default `GsonJsonParserFactory`; - `CustomGsonParserFactory`, which overrides `customize(GsonBuilder)` to - use a `LONG_OR_DOUBLE` number policy and disable HTML escaping. + use a `LONG_OR_DOUBLE` number policy and disable HTML escaping, and + implements `PayloadConverter` to convert each row's `payload` `Map` + into a `Payload` POJO via `gson.fromJson(gson.toJsonTree(...))`. + +Logged rows include the payload value's runtime class so the difference +between the default factories (which surface a `LinkedHashMap` / +`LinkedTreeMap`) and the customized factories (which surface a `Payload`) +shows up directly in the output. The build keeps both `jackson-databind` and `gson` on the classpath so the example can switch between processors at runtime. Production applications diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index bae569319..736796b0c 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -3,21 +3,23 @@ import com.clickhouse.client.api.Client; import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; +import com.clickhouse.client.api.data_formats.GsonJsonParserFactory; import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader; +import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory; import com.clickhouse.client.api.data_formats.JsonParserFactory; -import com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory; -import com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.data.ClickHouseFormat; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; import java.util.Map; /** @@ -29,7 +31,7 @@ * have its individual methods invoked.

* *

Two factories ship with the client and serve as the customization points: - * {@link com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory} and {@link com.clickhouse.client.api.data_formats.internal.GsonJsonParserFactory}. Extend + * {@link com.clickhouse.client.api.data_formats.JacksonJsonParserFactory} and {@link com.clickhouse.client.api.data_formats.GsonJsonParserFactory}. Extend * either of them and override the protected hook * ({@code createMapper()} for Jackson, {@code customize(GsonBuilder)} for Gson) * to plug in any library-level customization.

@@ -40,11 +42,13 @@ public class ClientV2JsonProcessorsExample { private static final String TABLE = "client_v2_json_processors_example"; - /** Sample dataset: {@code { id, name, active, score, payload }}. */ + /** + * Sample dataset: {@code { id, name, active, score, payload }}. + */ private static final Object[][] SAMPLE_ROWS = { - { 1, "alpha", true, 1.5, "{\"city\":\"Berlin\",\"tags\":[\"a\",\"b\"]}" }, - { 2, "beta", false, 2.5, "{\"city\":\"Paris\", \"tags\":[\"c\"]}" }, - { 3, "gamma", true, 3.5, "{\"city\":\"Tokyo\", \"tags\":[]}" }, + {1, "alpha", true, 1.5, "{\"city\":\"Berlin\",\"tags\":[\"a\",\"b\"]}"}, + {2, "beta", false, 2.5, "{\"city\":\"Paris\", \"tags\":[\"c\"]}"}, + {3, "gamma", true, 3.5, "{\"city\":\"Tokyo\", \"tags\":[]}"}, }; private final Client client; @@ -59,7 +63,9 @@ public static void main(String[] args) throws Exception { } } - /** Runs the full demo: prepares the table, loads sample rows, reads them four times. */ + /** + * Runs the full demo: prepares the table, loads sample rows, reads them four times. + */ public void run() throws Exception { recreateTable(); loadSampleData(); @@ -80,6 +86,12 @@ public void run() throws Exception { /** * Reads every row from {@link #TABLE} using a {@code JSONEachRow} stream * decoded with the supplied {@link JsonParserFactory}. + * + *

When the factory also implements {@link PayloadConverter} (as the two + * custom factories below do), the raw {@code payload} value is fed through + * {@link PayloadConverter#toPayload(Object)} so the row is logged with a + * typed {@link Payload} POJO instead of the bare {@code Map} + * produced by the underlying library.

*/ public void readAll(String label, JsonParserFactory factory) throws Exception { LOG.info("--- Reading rows with {} ---", label); @@ -87,18 +99,22 @@ public void readAll(String label, JsonParserFactory factory) throws Exception { QuerySettings settings = new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow); String sql = "SELECT id, name, active, score, payload FROM " + TABLE + " ORDER BY id"; + PayloadConverter converter = factory instanceof PayloadConverter ? (PayloadConverter) factory : null; + try (QueryResponse response = client.query(sql, settings).get(); ClickHouseTextFormatReader reader = new JSONEachRowFormatReader( factory.createJsonParser(response.getInputStream()))) { while (reader.next() != null) { - Map payload = reader.readValue("payload"); - LOG.info(" id={}, name={}, active={}, score={}, payload={}", + Object rawPayload = reader.readValue("payload"); + Object payload = converter != null ? converter.toPayload(rawPayload) : rawPayload; + LOG.info(" id={}, name={}, active={}, score={}, payload={} ({})", reader.getInteger("id"), reader.getString("name"), reader.getBoolean("active"), reader.getDouble("score"), - payload); + payload, + payload == null ? "null" : payload.getClass().getSimpleName()); } } } @@ -110,7 +126,9 @@ public void recreateTable() throws Exception { + ") ENGINE = MergeTree ORDER BY id"); } - /** Inserts {@link #SAMPLE_ROWS} into {@link #TABLE} as a single batched INSERT. */ + /** + * Inserts {@link #SAMPLE_ROWS} into {@link #TABLE} as a single batched INSERT. + */ public void loadSampleData() throws Exception { StringBuilder sql = new StringBuilder("INSERT INTO ").append(TABLE) .append(" (id, name, active, score, payload) VALUES"); @@ -152,21 +170,32 @@ private static String sqlString(String value) { // --------------------------------------------------------------------- /** - * Customized {@link com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory}. Override {@code createMapper()} + * Customized {@link JacksonJsonParserFactory}. Override {@code createMapper()} * to return any {@link ObjectMapper} you want — modules, feature flags, * deserializers, etc. all carry over to row parsing. * *

This example tolerates new server-side keys and preserves big integers - * and decimals exactly inside the {@code payload} JSON column.

+ * and decimals exactly inside the {@code payload} JSON column. It also + * implements {@link PayloadConverter} so the same configured mapper is + * reused to convert the row's {@code payload} {@code Map} into a typed + * {@link Payload} POJO via {@link ObjectMapper#convertValue(Object, Class)}.

*/ - public static final class CustomJacksonParserFactory extends com.clickhouse.client.api.data_formats.internal.JacksonJsonParserFactory { + public static final class CustomJacksonParserFactory extends JacksonJsonParserFactory implements PayloadConverter { + + private static final ObjectMapper MAPPER = JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true) + .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) + .build(); + @Override protected ObjectMapper createMapper() { - return JsonMapper.builder() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true) - .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) - .build(); + return MAPPER; + } + + @Override + public Payload toPayload(Object rawPayload) { + return MAPPER.convertValue(rawPayload, Payload.class); } } @@ -178,13 +207,74 @@ protected ObjectMapper createMapper() { * *

This example parses integer-shaped JSON numbers as {@code Long} (the * default is {@code Double}, which loses precision for large {@code Int64} - * values) and disables HTML escaping on round-trips.

+ * values) and disables HTML escaping on round-trips. It also implements + * {@link PayloadConverter} so the same configured {@link Gson} is reused + * to convert the row's {@code payload} {@code Map} into a typed + * {@link Payload} POJO via {@code fromJson(toJsonTree(...))}.

*/ - public static final class CustomGsonParserFactory extends GsonJsonParserFactory { + public static final class CustomGsonParserFactory extends GsonJsonParserFactory implements PayloadConverter { + + private static final Gson GSON = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .disableHtmlEscaping() + .setLenient() + .create(); + @Override protected void customize(GsonBuilder builder) { builder.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping(); } + + @Override + public Payload toPayload(Object rawPayload) { + return GSON.fromJson(GSON.toJsonTree(rawPayload), Payload.class); + } + } + + // --------------------------------------------------------------------- + // Domain types used to demonstrate POJO materialization of payload + // --------------------------------------------------------------------- + + /** + * Optional hook implemented by customized factories that know how to turn + * the raw {@code payload} value (a {@code Map} produced by + * the underlying JSON library) into a typed {@link Payload} POJO. The + * default factories do not implement it, so {@link #readAll(String, JsonParserFactory)} + * logs the raw map for them. + */ + public interface PayloadConverter { + Payload toPayload(Object rawPayload); + } + + /** POJO shape of the {@code payload} JSON column used by the sample data. */ + public static final class Payload { + + private String city; + private List tags; + + public Payload() { + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + @Override + public String toString() { + return "Payload{city='" + city + "', tags=" + tags + '}'; + } } } diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md index c22f2e5ad..a8022af47 100644 --- a/examples/jdbc-v2-json-processors/README.md +++ b/examples/jdbc-v2-json-processors/README.md @@ -2,12 +2,87 @@ ## Overview -This standalone example shows how to configure `jdbc-v2` to read -`FORMAT JSONEachRow` results with both supported JSON processors using one -shared table and one shared dataset: - -- `JACKSON` -- `GSON` +This standalone example shows how to consume `FORMAT JSONEachRow` responses +through `jdbc-v2` with the two factories shipped under +`com.clickhouse.client.api.data_formats`: + +- `JacksonJsonParserFactory` +- `GsonJsonParserFactory` + +### How the JDBC driver selects a factory + +The driver picks the parser factory **by fully-qualified class name** from +the `JSON_PARSER_FACTORY` driver property (key: `jdbc_json_parser_factor`). +The value is the FQN of a class that implements `JsonParserFactory`; the +driver loads it reflectively and instantiates it through a **public no-arg +constructor**. There is no enum-style selector. + +Selection is **connection-level**: the factory cannot be swapped on an +existing connection. The driver instantiates the named class once during +connection creation and reuses that instance for every `JSONEachRow` +response served by the connection. + +### Customization is done by extending the factory + +Because the driver instantiates the named class with a no-arg constructor, +customization cannot be expressed as constructor arguments. The supported +approach is: + +1. Subclass `JacksonJsonParserFactory` or `GsonJsonParserFactory` in your + own code. +2. Override the protected hook: + - `protected ObjectMapper createMapper()` on `JacksonJsonParserFactory` — + return any fully-configured `ObjectMapper` (modules, feature flags, + deserializers). + - `protected void customize(GsonBuilder builder)` on + `GsonJsonParserFactory` — configure the `GsonBuilder` (number policy, + `TypeAdapter`s, date format, ...). The factory still applies + `setLenient()` on its own afterwards, which is required for + `JSONEachRow`. +3. Set `JSON_PARSER_FACTORY` to the FQN of the subclass. + +This example carries both subclasses as `public static final` nested classes +inside `JdbcV2JsonProcessorsExample` (`CustomJacksonParserFactory` and +`CustomGsonParserFactory`). The example feeds their FQNs to the driver via +`factoryClass.getName()`, which for nested classes returns the +`Outer$Inner` binary form — accepted by `Class.forName(...)` and by the +driver. If you set `JSON_PARSER_FACTORY` manually (e.g. from a config file +or JDBC URL) and your subclass is nested, you must use the same `$`-form; +top-level classes use the ordinary dot-separated FQN. + +Both custom subclasses also implement a tiny `PayloadConverter` interface +defined inside the example: their configured `ObjectMapper` / `Gson` is +reused to convert the raw `payload` value produced by the underlying +library into a typed `Payload` POJO. Because the JDBC driver only exposes +the factory through the connection (not as a Java object), `readAll(...)` +detects the interface on the factory **class** and instantiates its own +converter via the same public no-arg constructor the driver uses. The +default factories do not implement the interface, so `readAll(...)` logs +the raw map for them — making the contrast between the default behavior +and the customized behavior visible in the output. + +### Component shape + +`JdbcV2JsonProcessorsExample` is written as a small component: + +- `JdbcV2JsonProcessorsExample(String url, String user, String password)` + holds the connection settings and exposes regular instance methods + (`recreateTable()`, `loadSampleData()`, `readAll(label, factoryClass)`, + `run()`), so the class can be copied as-is into another project and have + its individual methods invoked. +- Sample rows are kept in a plain `Object[][]` constant, separate from the + SQL, so the read path stays focused on the parser-factory wiring. + +Each read call in `run()` follows the same three-step shape: + +1. **Pick a factory class** — `JacksonJsonParserFactory.class` / + `GsonJsonParserFactory.class` for defaults, or one of the nested custom + subclasses. +2. **Customize if needed** — only inside the subclass, by overriding the + protected hook. +3. **Execute** — `readAll(label, factoryClass)` opens a fresh connection + with `JSON_PARSER_FACTORY=`, runs the `SELECT ... FORMAT JSONEachRow` + and iterates the `ResultSet`. ## Requirements @@ -25,9 +100,9 @@ gradle run Connection properties can be supplied as system properties: -- `-DchUrl` - JDBC URL (default: `jdbc:clickhouse://localhost:8123/default`) -- `-DchUser` - ClickHouse user name (default: `default`) -- `-DchPassword` - ClickHouse user password (default: empty) +- `-DchUrl` — JDBC URL (default: `jdbc:clickhouse://localhost:8123/default`) +- `-DchUser` — ClickHouse user name (default: `default`) +- `-DchPassword` — ClickHouse user password (default: empty) Example with custom connection properties: @@ -42,15 +117,32 @@ gradle run \ `com.clickhouse.examples.jdbc_v2.json_processors.JdbcV2JsonProcessorsExample` -- Runs the following steps in order: - 1. defines table `jdbc_v2_json_processors_example` with primitive columns and - one `payload JSON` column; - 2. loads sample rows from `src/main/resources/sample_data.csv` into that table; - 3. reads the same rows with `runGsonExample(...)`; - 4. reads the same rows again with `runJacksonExample(...)`. -- Reads rows back through `ResultSet` and logs the primitive columns together - with the parsed JSON object from `payload`. +Steps performed by `run()`: + +1. `recreateTable()` — drops and re-creates `jdbc_v2_json_processors_example` + with primitive columns and one `payload JSON` column. +2. `loadSampleData()` — inserts the rows from the `SAMPLE_ROWS` array as a + single batched `INSERT`. +3. `readAll(...)` is invoked four times, each time pointing + `JSON_PARSER_FACTORY` at a different class: + - `JacksonJsonParserFactory` — default Jackson; + - `JdbcV2JsonProcessorsExample.CustomJacksonParserFactory` — nested + subclass overriding `createMapper()` to tolerate unknown properties + and preserve big integers and decimals exactly, also implementing + `PayloadConverter` to convert each row's `payload` `Map` into a + `Payload` POJO via `ObjectMapper.convertValue(...)`; + - `GsonJsonParserFactory` — default Gson; + - `JdbcV2JsonProcessorsExample.CustomGsonParserFactory` — nested + subclass overriding `customize(GsonBuilder)` to use a `LONG_OR_DOUBLE` + number policy and disable HTML escaping, also implementing + `PayloadConverter` to convert each row's `payload` `Map` into a + `Payload` POJO via `gson.fromJson(gson.toJsonTree(...))`. + +Logged rows include the payload value's runtime class so the difference +between the default factories (which surface a `LinkedHashMap` / +`LinkedTreeMap`) and the customized factories (which surface a `Payload`) +shows up directly in the output. The build keeps both `jackson-databind` and `gson` on the classpath so the -example can switch between processors at runtime. Production applications only -need to keep the processor they actually use. +example can switch between processors at runtime. Production applications +only need to keep the processor they actually use. diff --git a/examples/jdbc-v2-json-processors/build.gradle.kts b/examples/jdbc-v2-json-processors/build.gradle.kts index 65b501e3e..07ed830d2 100644 --- a/examples/jdbc-v2-json-processors/build.gradle.kts +++ b/examples/jdbc-v2-json-processors/build.gradle.kts @@ -29,7 +29,7 @@ application { } tasks.named("run") { - listOf("chUrl", "chUser", "chPassword", "jsonProcessor").forEach { key -> + listOf("chUrl", "chUser", "chPassword").forEach { key -> System.getProperty(key)?.let { value -> systemProperty(key, value) } diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index 58a465a1b..cbbf25e36 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -1,102 +1,188 @@ package com.clickhouse.examples.jdbc_v2.json_processors; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.data_formats.GsonJsonParserFactory; +import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory; +import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.clickhouse.jdbc.Driver; +import com.clickhouse.jdbc.DriverProperties; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Statement; -import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Demonstrates how to consume {@code FORMAT JSONEachRow} responses through + * {@code jdbc-v2} with the two factories shipped under + * {@link com.clickhouse.client.api.data_formats}. + * + *

The class is intentionally written as a regular component (instance + * methods, shared connection settings as fields) so it can be copied as-is + * into other projects and have its individual methods invoked.

+ * + *

The JDBC driver selects a parser factory from the + * {@link DriverProperties#JSON_PARSER_FACTORY} driver property — its value is + * the fully-qualified class name of a {@link JsonParserFactory} + * implementation, which the driver loads reflectively and instantiates via + * a public no-arg constructor. There is no enum-style selector.

+ * + *

For Jackson/Gson customization the recommended approach is therefore to + * extend the shipped factory and override its protected hook + * ({@code createMapper()} or {@code customize(GsonBuilder)}), then point + * {@code JSON_PARSER_FACTORY} at the FQN of the subclass. See + * {@link CustomJacksonParserFactory} and {@link CustomGsonParserFactory} + * below.

+ */ public class JdbcV2JsonProcessorsExample { + private static final Logger LOG = LoggerFactory.getLogger(JdbcV2JsonProcessorsExample.class); - private static final String TABLE_NAME = "jdbc_v2_json_processors_example"; - private static final String SAMPLE_DATA_RESOURCE = "/sample_data.csv"; - private static final String CREATE_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " (" - + "id UInt32, " - + "name String, " - + "active Bool, " - + "score Float64, " - + "payload JSON" - + ") ENGINE = MergeTree ORDER BY id"; - private static final String SELECT_DATA_SQL = "SELECT id, name, active, score, payload " - + "FROM " + TABLE_NAME + " ORDER BY id FORMAT JSONEachRow"; + + private static final String TABLE = "jdbc_v2_json_processors_example"; + + /** + * Sample dataset: {@code { id, name, active, score, payload }}. + */ + private static final Object[][] SAMPLE_ROWS = { + {1, "alpha", true, 1.5, "{\"city\":\"Berlin\",\"tags\":[\"a\",\"b\"]}"}, + {2, "beta", false, 2.5, "{\"city\":\"Paris\", \"tags\":[\"c\"]}"}, + {3, "gamma", true, 3.5, "{\"city\":\"Tokyo\", \"tags\":[]}"}, + }; + + private final String url; + private final String user; + private final String password; + + public JdbcV2JsonProcessorsExample(String url, String user, String password) { + this.url = url; + this.user = user; + this.password = password; + } public static void main(String[] args) throws Exception { + // jdbc-v2 does not self-register from a static initializer; standalone + // examples must register the driver explicitly before calling DriverManager. + Driver.load(); + String url = System.getProperty("chUrl", "jdbc:clickhouse://localhost:8123/default"); String user = System.getProperty("chUser", "default"); String password = System.getProperty("chPassword", ""); - Properties setupProperties = baseProperties(user, password); + new JdbcV2JsonProcessorsExample(url, user, password).run(); + } - registerDriver(); - defineTableStructure(url, setupProperties); - loadData(url, setupProperties); + /** + * Runs the full demo: prepares the table, loads sample rows, reads them four times. + */ + public void run() throws Exception { + recreateTable(); + loadSampleData(); - runGsonExample(url, user, password); - runJacksonExample(url, user, password); - } + // 1. Default Jackson: factory FQN points to the shipped class. + readAll("Jackson (default)", JacksonJsonParserFactory.class); - private static void defineTableStructure(String url, Properties properties) throws Exception { - LOG.info("Step 1. Defining table structure: {}", TABLE_NAME); - try (Connection connection = DriverManager.getConnection(url, properties); - Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE IF EXISTS " + TABLE_NAME); - statement.execute(CREATE_TABLE_SQL); - } - } + // 2. Customized Jackson: factory FQN points to a subclass of + // JacksonJsonParserFactory whose no-arg constructor overrides createMapper(). + readAll("Jackson (custom)", CustomJacksonParserFactory.class); - private static void loadData(String url, Properties properties) throws Exception { - List rows = readSampleRows(); - LOG.info("Step 2. Loading {} sample rows from {} into {}", rows.size(), SAMPLE_DATA_RESOURCE, TABLE_NAME); - try (Connection connection = DriverManager.getConnection(url, properties); - Statement statement = connection.createStatement()) { - statement.execute("TRUNCATE TABLE " + TABLE_NAME); - statement.executeUpdate(buildInsertSql(rows)); - } - } + // 3. Default Gson. + readAll("Gson (default)", GsonJsonParserFactory.class); - private static void runJacksonExample(String url, String user, String password) throws Exception { - Properties properties = baseProperties(user, password); - properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); - LOG.info("Step 4. Running jdbc-v2 example with Jackson"); - readRows(url, properties, "JACKSON"); + // 4. Customized Gson: subclass overriding customize(GsonBuilder). + readAll("Gson (custom)", CustomGsonParserFactory.class); } - private static void runGsonExample(String url, String user, String password) throws Exception { - Properties properties = baseProperties(user, password); - properties.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "GSON"); - LOG.info("Step 3. Running jdbc-v2 example with Gson"); - readRows(url, properties, "GSON"); - } + /** + * Reads every row from {@link #TABLE} through a fresh JDBC connection + * configured to use the supplied {@link JsonParserFactory} implementation. + * + *

Selection is connection-level: the factory cannot be swapped on an + * existing connection. The driver instantiates the named class once during + * connection creation and reuses that instance for every {@code JSONEachRow} + * response served by the connection.

+ * + *

When {@code factoryClass} also implements {@link PayloadConverter} + * (as the two custom factories below do), the method also instantiates a + * converter — through the same public no-arg constructor the driver uses — + * and feeds {@code rs.getObject("payload")} through + * {@link PayloadConverter#toPayload(Object)}, so the row is logged with a + * typed {@link Payload} POJO instead of the bare {@code Map} + * produced by the underlying library.

+ */ + public void readAll(String label, Class factoryClass) throws Exception { + LOG.info("--- Reading rows with {} ({}) ---", label, factoryClass.getName()); + + Properties props = baseProperties(); + props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), factoryClass.getName()); - private static void readRows(String url, Properties properties, String processor) throws Exception { - try (Connection connection = DriverManager.getConnection(url, properties); + PayloadConverter converter = PayloadConverter.class.isAssignableFrom(factoryClass) + ? (PayloadConverter) factoryClass.getDeclaredConstructor().newInstance() + : null; + + String sql = "SELECT id, name, active, score, payload FROM " + TABLE + + " ORDER BY id FORMAT JSONEachRow"; + + try (Connection connection = DriverManager.getConnection(url, props); Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(SELECT_DATA_SQL)) { + ResultSet rs = statement.executeQuery(sql)) { + while (rs.next()) { - Object payload = rs.getObject("payload"); - LOG.info("[{}] id={}, name={}, active={}, score={}, payload={}", - processor, + Object rawPayload = rs.getObject("payload"); + Object payload = converter != null ? converter.toPayload(rawPayload) : rawPayload; + LOG.info(" id={}, name={}, active={}, score={}, payload={} ({})", rs.getInt("id"), rs.getString("name"), rs.getBoolean("active"), rs.getDouble("score"), - payload); + payload, + payload == null ? "null" : payload.getClass().getSimpleName()); } } } - private static Properties baseProperties(String user, String password) { + public void recreateTable() throws SQLException { + try (Connection connection = DriverManager.getConnection(url, baseProperties()); + Statement statement = connection.createStatement()) { + statement.execute("DROP TABLE IF EXISTS " + TABLE); + statement.execute("CREATE TABLE " + TABLE + " (" + + "id UInt32, name String, active Bool, score Float64, payload JSON" + + ") ENGINE = MergeTree ORDER BY id"); + } + } + + /** + * Inserts {@link #SAMPLE_ROWS} into {@link #TABLE} as a single batched INSERT. + */ + public void loadSampleData() throws SQLException { + StringBuilder sql = new StringBuilder("INSERT INTO ").append(TABLE) + .append(" (id, name, active, score, payload) VALUES"); + for (int i = 0; i < SAMPLE_ROWS.length; i++) { + Object[] row = SAMPLE_ROWS[i]; + sql.append(i == 0 ? " " : ", ") + .append('(').append(row[0]) + .append(", ").append(sqlString((String) row[1])) + .append(", ").append(row[2]) + .append(", ").append(row[3]) + .append(", ").append(sqlString((String) row[4])) + .append(')'); + } + try (Connection connection = DriverManager.getConnection(url, baseProperties()); + Statement statement = connection.createStatement()) { + statement.executeUpdate(sql.toString()); + } + } + + private Properties baseProperties() { Properties properties = new Properties(); properties.setProperty("user", user); properties.setProperty("password", password); @@ -104,118 +190,137 @@ private static Properties baseProperties(String user, String password) { return properties; } - private static void registerDriver() { - // `jdbc-v2` does not self-register from its static initializer, so standalone - // examples should register it explicitly before calling DriverManager. - Driver.load(); + private static String sqlString(String value) { + return "'" + value.replace("'", "''") + "'"; } - private static List readSampleRows() throws IOException { - InputStream stream = JdbcV2JsonProcessorsExample.class.getResourceAsStream(SAMPLE_DATA_RESOURCE); - if (stream == null) { - throw new IOException("Resource not found: " + SAMPLE_DATA_RESOURCE); - } + // --------------------------------------------------------------------- + // Customized factories + // --------------------------------------------------------------------- - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { - String header = reader.readLine(); - if (header == null) { - throw new IOException("CSV resource is empty: " + SAMPLE_DATA_RESOURCE); - } + /** + * Customized {@link JacksonJsonParserFactory}. Override {@code createMapper()} + * to return any {@link ObjectMapper} you want — modules, feature flags, + * deserializers, etc. all carry over to row parsing. + * + *

This class must be {@code public static} with a public no-arg + * constructor because the JDBC driver loads it reflectively via the + * {@code jdbc_json_parser_factor} driver property; the {@code .getName()} + * of a nested class is the {@code Outer$Inner} binary form, which + * {@code Class.forName(...)} accepts.

+ * + *

This example tolerates new server-side keys and preserves big integers + * and decimals exactly inside the {@code payload} JSON column. It also + * implements {@link PayloadConverter} so the same configured mapper is + * reused to convert the row's {@code payload} {@code Map} into a typed + * {@link Payload} POJO via {@link ObjectMapper#convertValue(Object, Class)}.

+ */ + public static final class CustomJacksonParserFactory extends JacksonJsonParserFactory implements PayloadConverter { - List rows = new ArrayList<>(); - String line; - while ((line = reader.readLine()) != null) { - if (line.trim().isEmpty()) { - continue; - } - - List values = parseCsvLine(line); - if (values.size() != 5) { - throw new IOException("Expected 5 columns in sample CSV but found " + values.size() + ": " + line); - } - - rows.add(new SampleRow( - Integer.parseInt(values.get(0)), - values.get(1), - Boolean.parseBoolean(values.get(2)), - Double.parseDouble(values.get(3)), - values.get(4))); - } + private static final ObjectMapper MAPPER = JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true) + .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) + .build(); + + @Override + protected ObjectMapper createMapper() { + return MAPPER; + } - return rows; + @Override + public Payload toPayload(Object rawPayload) { + return MAPPER.convertValue(rawPayload, Payload.class); } } - private static List parseCsvLine(String line) { - List values = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean inQuotes = false; - - for (int i = 0; i < line.length(); i++) { - char ch = line.charAt(i); - if (ch == '"') { - if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { - current.append('"'); - i++; - } else { - inQuotes = !inQuotes; - } - } else if (ch == ',' && !inQuotes) { - values.add(current.toString()); - current.setLength(0); - } else { - current.append(ch); - } + /** + * Customized {@link GsonJsonParserFactory}. Override + * {@code customize(GsonBuilder)} and configure the builder; the factory + * applies {@code setLenient()} on its own afterwards (which is required for + * the stream-of-objects shape of {@code JSONEachRow}). + * + *

This class must be {@code public static} with a public no-arg + * constructor because the JDBC driver loads it reflectively via the + * {@code jdbc_json_parser_factor} driver property; the {@code .getName()} + * of a nested class is the {@code Outer$Inner} binary form, which + * {@code Class.forName(...)} accepts.

+ * + *

This example parses integer-shaped JSON numbers as {@code Long} (the + * default is {@code Double}, which loses precision for large {@code Int64} + * values) and disables HTML escaping on round-trips. It also implements + * {@link PayloadConverter} so the same configured {@link Gson} is reused + * to convert the row's {@code payload} {@code Map} into a typed + * {@link Payload} POJO via {@code fromJson(toJsonTree(...))}.

+ */ + public static final class CustomGsonParserFactory extends GsonJsonParserFactory implements PayloadConverter { + + private static final Gson GSON = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .disableHtmlEscaping() + .setLenient() + .create(); + + @Override + protected void customize(GsonBuilder builder) { + builder.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .disableHtmlEscaping(); } - values.add(current.toString()); - return values; + @Override + public Payload toPayload(Object rawPayload) { + return GSON.fromJson(GSON.toJsonTree(rawPayload), Payload.class); + } + } + + // --------------------------------------------------------------------- + // Domain types used to demonstrate POJO materialization of payload + // --------------------------------------------------------------------- + + /** + * Optional hook implemented by customized factories that know how to turn + * the raw {@code payload} value (a {@code Map} produced by + * the underlying JSON library) into a typed {@link Payload} POJO. The + * default factories do not implement it, so + * {@link #readAll(String, Class)} logs the raw map for them. + * + *

The interface is invoked from application code, not from the JDBC + * driver itself: {@code readAll(...)} detects it on the factory class and + * instantiates its own converter through the same public no-arg constructor + * the driver uses for row parsing.

+ */ + public interface PayloadConverter { + Payload toPayload(Object rawPayload); } - private static String buildInsertSql(List rows) { - if (rows.isEmpty()) { - throw new IllegalArgumentException("Sample CSV does not contain any rows"); + /** POJO shape of the {@code payload} JSON column used by the sample data. */ + public static final class Payload { + + private String city; + private List tags; + + public Payload() { } - StringBuilder sql = new StringBuilder("INSERT INTO ") - .append(TABLE_NAME) - .append(" (id, name, active, score, payload) VALUES "); - - for (SampleRow row : rows) { - sql.append('(') - .append(row.id) - .append(", ") - .append(quoteSqlString(row.name)) - .append(", ") - .append(row.active) - .append(", ") - .append(row.score) - .append(", ") - .append(quoteSqlString(row.payload)) - .append("), "); + public String getCity() { + return city; } - sql.setLength(sql.length() - 2); - return sql.toString(); - } + public void setCity(String city) { + this.city = city; + } - private static String quoteSqlString(String value) { - return "'" + value.replace("'", "''") + "'"; - } + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } - private static final class SampleRow { - private final int id; - private final String name; - private final boolean active; - private final double score; - private final String payload; - - private SampleRow(int id, String name, boolean active, double score, String payload) { - this.id = id; - this.name = name; - this.active = active; - this.score = score; - this.payload = payload; + @Override + public String toString() { + return "Payload{city='" + city + "', tags=" + tags + '}'; } } } From 11950bd4764efb9cac3552351435b1c78d547626 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 20 May 2026 17:14:21 -0700 Subject: [PATCH 17/26] removed generate artifacts. added more documentation --- .../com/clickhouse/client/api/Client.java | 1 - .../data_formats/GsonJsonParserFactory.java | 4 + .../JacksonJsonParserFactory.java | 1 + .../client/api/data_formats/JsonParser.java | 2 + .../AbstractJSONEachRowFormatReaderTests.java | 1 - docs/client-v2-json-support.md | 218 ++++++++------- examples/client-v2-json-processors/README.md | 30 +++ examples/jdbc-dispatcher-demo/.gitignore | 2 + .../.gradle/8.8/checksums/checksums.lock | Bin 17 -> 0 bytes .../.gradle/8.8/checksums/md5-checksums.bin | Bin 18897 -> 0 bytes .../.gradle/8.8/checksums/sha1-checksums.bin | Bin 19091 -> 0 bytes .../8.8/dependencies-accessors/gc.properties | 0 .../8.8/executionHistory/executionHistory.bin | Bin 45038 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/8.8/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/8.8/fileHashes/fileHashes.bin | Bin 19997 -> 0 bytes .../.gradle/8.8/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../8.8/fileHashes/resourceHashesCache.bin | Bin 18939 -> 0 bytes .../.gradle/8.8/gc.properties | 0 .../buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 - .../buildOutputCleanup/outputFiles.bin | Bin 19037 -> 0 bytes .../.gradle/file-system.probe | Bin 8 -> 0 bytes .../.gradle/vcs-1/gc.properties | 0 .../jdbc-dispatcher-demo-1.0.0-SNAPSHOT.tar | Bin 171008 -> 0 bytes .../jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip | Bin 143537 -> 0 bytes .../resources/main/simplelogger.properties | 18 -- .../build/scripts/jdbc-dispatcher-demo | 248 ------------------ .../build/scripts/jdbc-dispatcher-demo.bat | 92 ------- .../stash-dir/DispatcherDemo.class.uniqueId5 | Bin 10873 -> 0 bytes ...cherService$DriversHandler.class.uniqueId1 | Bin 3465 -> 0 bytes ...tcherService$HealthHandler.class.uniqueId0 | Bin 1494 -> 0 bytes ...atcherService$QueryHandler.class.uniqueId3 | Bin 4109 -> 0 bytes ...cherService$VersionHandler.class.uniqueId2 | Bin 3552 -> 0 bytes .../DispatcherService.class.uniqueId4 | Bin 10339 -> 0 bytes .../compileJava/previous-compilation-data.bin | Bin 1974 -> 0 bytes .../build/tmp/jar/MANIFEST.MF | 2 - examples/jdbc-v2-json-processors/README.md | 42 +++ .../JdbcV2JsonProcessorsExample.java | 4 + 39 files changed, 214 insertions(+), 453 deletions(-) create mode 100644 examples/jdbc-dispatcher-demo/.gitignore delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/sha1-checksums.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.lock delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileChanges/last-build.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/fileHashes.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/fileHashes.lock delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/fileHashes/resourceHashesCache.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/cache.properties delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/file-system.probe delete mode 100644 examples/jdbc-dispatcher-demo/.gradle/vcs-1/gc.properties delete mode 100644 examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.tar delete mode 100644 examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip delete mode 100644 examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties delete mode 100755 examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo delete mode 100644 examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$QueryHandler.class.uniqueId3 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin delete mode 100644 examples/jdbc-dispatcher-demo/build/tmp/jar/MANIFEST.MF diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 92f3db282..c95b51c72 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -266,7 +266,6 @@ public static class Builder { private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy; private Object metricRegistry = null; private Supplier queryIdGenerator; - private JsonParserFactory jsonParserFactory; public Builder() { this.endpoints = new HashSet<>(); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java index 2444fffed..603cb3078 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/GsonJsonParserFactory.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; @@ -22,6 +23,9 @@ public GsonJsonParserFactory() { } protected void customize(GsonBuilder builder) { + // JSONEachRow numbers may represent UInt64 or Decimal values, so avoid + // Gson's default Double materialization and preserve the original value. + builder.setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL); } @Override diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java index fd797e1ba..0730be9c0 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JacksonJsonParserFactory.java @@ -14,6 +14,7 @@ public JacksonJsonParserFactory() { } protected ObjectMapper createMapper() { + // override this method to customize object mapper return new ObjectMapper(); } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java index 3a61f50fa..e0a271b44 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JsonParser.java @@ -6,6 +6,8 @@ * Interface for JSON row processors. */ public interface JsonParser extends AutoCloseable { + + /** * Reads next row from the input stream. * @return map of column names to values, or null if no more rows diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index c162621ae..2f37977a5 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -5,7 +5,6 @@ import com.clickhouse.client.ClickHouseProtocol; import com.clickhouse.client.ClickHouseServerForTest; import com.clickhouse.client.api.Client; -import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.enums.Protocol; import com.clickhouse.client.api.query.QueryResponse; import com.clickhouse.client.api.query.QuerySettings; diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 5bd859b6a..0d8cee085 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -33,12 +33,11 @@ provides additional advantages beyond what the format alone delivers: materializes one row at a time, so peak memory consumption is bounded by the size of the current row rather than by the size of the result set. - **Reuse of an existing JSON dependency on the classpath.** Applications - that already depend on Jackson or Gson for unrelated purposes can select - the matching processor through `JSON_PROCESSOR` and avoid contributing a - second JSON library to the runtime classpath. Only the library JARs are - shared; the reader uses its own default `ObjectMapper` or `Gson` instance - and does not pick up the application's configured modules, `TypeAdapter`s, - or other customizations. + that already depend on Jackson or Gson for unrelated purposes can instantiate + the matching `JsonParserFactory` and avoid contributing a second JSON library + to the runtime classpath. Only the library JARs are shared; the default + factories use their own `ObjectMapper` or `Gson` instance unless callers + provide a subclass with custom settings. - **Choice between processors.** Jackson and Gson are selected independently and can be swapped at deployment time. Applications may pick the processor that best matches their existing classpath and operational constraints, @@ -59,20 +58,15 @@ provides additional advantages beyond what the format alone delivers: - Adds `com.clickhouse.client.api.data_formats.JSONEachRowFormatReader`, which implements `ClickHouseTextFormatReader` over a streaming JSON parser. -- Adds `Client.newTextFormatReader(QueryResponse)` as the dedicated factory - for text output formats. Currently it accepts - `ClickHouseFormat.JSONEachRow` and returns a `ClickHouseTextFormatReader`. - `Client.newBinaryFormatReader(...)` continues to construct the binary +- `Client.newBinaryFormatReader(...)` continues to construct the binary readers (`Native`, `RowBinary`, `RowBinaryWithNames`, `RowBinaryWithNamesAndTypes`) and rejects text formats with - `IllegalArgumentException`. -- Introduces an internal JSON parser SPI under - `com.clickhouse.client.api.data_formats.internal`, consisting of - `JsonParser`, `JsonParserFactory`, `JacksonJsonParser`, and - `GsonJsonParser`. -- Adds the client option `ClientConfigProperties.JSON_PROCESSOR` - (key `json_processor`), with default value `JACKSON` and accepted values - `JACKSON` and `GSON`. + `IllegalArgumentException`. `JSONEachRow` callers construct + `JSONEachRowFormatReader` directly from a `JsonParser`. +- Introduces a JSON parser SPI under + `com.clickhouse.client.api.data_formats`, consisting of `JsonParser`, + `JsonParserFactory`, `JacksonJsonParserFactory`, and + `GsonJsonParserFactory`. - Forces a fixed set of server settings for `JSONEachRow` requests so that the response stream contains plain JSON numbers (see [Forced server settings](#forced-server-settings-for-jsoneachrow)). @@ -83,8 +77,9 @@ provides additional advantages beyond what the format alone delivers: - Modifies `StatementImpl.executeQuery(...)` to accept `JSONEachRow` as a valid output format. All other text formats remain unsupported. -- Forwards the `json_processor` option to the underlying `client-v2` - configuration; it is configured through `Properties` or the JDBC URL. +- Adds `DriverProperties.JSON_PARSER_FACTORY` + (key `jdbc_json_parser_factor`) for selecting the `JsonParserFactory` + implementation by fully-qualified class name. - Declares Jackson and Gson as `provided` dependencies, consistent with `client-v2`. @@ -124,9 +119,9 @@ package com.clickhouse.client.api.data_formats; public class JSONEachRowFormatReader implements ClickHouseTextFormatReader { ... } ``` -The reader is normally instantiated by `Client.newTextFormatReader(...)`. -The class is public so that callers can construct it from any -`InputStream`-backed `JsonParser`. +The reader is instantiated from an `InputStream`-backed `JsonParser`. Callers +usually create that parser through `JacksonJsonParserFactory`, +`GsonJsonParserFactory`, or an application subclass of one of those factories. `JSONEachRow` is a text format, so the reader implements `ClickHouseTextFormatReader`. Callers that need to handle both binary and @@ -135,26 +130,24 @@ text readers uniformly can program against `ClickHouseFormatReader`. ### `JsonParser` SPI ```java -package com.clickhouse.client.api.data_formats.internal; +package com.clickhouse.client.api.data_formats; public interface JsonParser extends AutoCloseable { Map nextRow() throws Exception; } ``` -Two implementations are provided: +Two factories are provided: -- `JacksonJsonParser` uses `com.fasterxml.jackson.core` and +- `JacksonJsonParserFactory` uses `com.fasterxml.jackson.core` and `com.fasterxml.jackson.databind` to stream JSON objects. -- `GsonJsonParser` uses `com.google.gson` with a lenient `JsonReader`, +- `GsonJsonParserFactory` uses `com.google.gson` with a lenient `JsonReader`, which accepts a sequence of top-level JSON objects separated by whitespace, as produced by `JSONEachRow`. -`JsonParserFactory.createParser(String type, InputStream)` selects an -implementation based on the value of the `JSON_PROCESSOR` option. -Implementations are loaded reflectively. When the requested implementation is -unavailable, the factory throws a `RuntimeException` whose message identifies -the missing dependency. +`JsonParserFactory.createJsonParser(InputStream)` creates a parser for each +response stream. The factory may hold reusable parser configuration, but the +returned `JsonParser` is request-scoped and owns the stream reader it creates. The shipped implementations construct their own `ObjectMapper` and `Gson` instances with default settings. To customize the underlying library @@ -172,45 +165,42 @@ Customization that does not need to influence the underlying parser can still be performed on the caller side, after the row has been materialized as `Map`. -### `JSON_PROCESSOR` configuration property +### JDBC parser factory property -Defined in `ClientConfigProperties`: +`jdbc-v2` selects the parser factory through +`DriverProperties.JSON_PARSER_FACTORY`: -| Property key | Default | Accepted values | -| ----------------- | --------- | ----------------- | -| `json_processor` | `JACKSON` | `JACKSON`, `GSON` | +| Property key | Value | +| ------------------------- | ----- | +| `jdbc_json_parser_factor` | Fully-qualified class name implementing `JsonParserFactory` | -The same property is used by the `client-v2` builder -(`ClientConfigProperties.JSON_PROCESSOR.getKey()`) and by the JDBC driver, -where it is supplied through `Properties` or the JDBC URL query string. +The named class is loaded reflectively when the connection is created and must +have a public no-argument constructor. There is no equivalent `client-v2` +configuration key; direct client users pass a factory instance to their own +reader construction code. ## Runtime dependencies `client-v2` and `jdbc-v2` declare the JSON libraries with `provided` scope, so that they are not contributed to the runtime classpath of applications -that do not require them. Applications must add exactly one of the following -to their runtime classpath: +that do not require them. Applications must add the JSON library used by the +selected factory to their runtime classpath: - Jackson — `com.fasterxml.jackson.core:jackson-databind`, `com.fasterxml.jackson.core:jackson-core`, `com.fasterxml.jackson.core:jackson-annotations` - (required when `JSON_PROCESSOR=JACKSON`, the default); or + (required when using `JacksonJsonParserFactory`); or - Gson — `com.google.code.gson:gson` - (required when `JSON_PROCESSOR=GSON`). + (required when using `GsonJsonParserFactory`). The repository builds against Jackson `2.17.2` and Gson `2.10.1`. The parser implementations rely only on the streaming token API and a single `Map` materialization call, so other recent versions of either library are expected to be compatible. -When the configured processor is not present on the classpath at the time a -`JSONEachRow` reader is constructed, `JsonParserFactory` throws a -`RuntimeException` with the following message: - -```text -JSON processor class not found: com.clickhouse.client.api.data_formats.JacksonJsonParserFactory. -Make sure you have the required library (Jackson or Gson) on your classpath. -``` +When the selected JSON library is not present on the classpath, construction +or first use of the corresponding factory fails with the dependency-loading +error raised by the JVM or the JSON library. ## Usage in `client-v2` @@ -221,17 +211,18 @@ Client client = new Client.Builder() .setPassword("") .setDefaultDatabase("default") .serverSetting("allow_experimental_json_type", "1") - .setOption(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON") // or "GSON" .build(); +JsonParserFactory parserFactory = new JacksonJsonParserFactory(); + QuerySettings settings = new QuerySettings() .setFormat(ClickHouseFormat.JSONEachRow); try (QueryResponse response = client.query( "SELECT id, name, active, score, payload FROM events ORDER BY id", - settings).get()) { - - ClickHouseTextFormatReader reader = client.newTextFormatReader(response); + settings).get(); + ClickHouseTextFormatReader reader = new JSONEachRowFormatReader( + parserFactory.createJsonParser(response.getInputStream()))) { while (reader.next() != null) { int id = reader.getInteger("id"); String name = reader.getString("name"); @@ -245,15 +236,15 @@ try (QueryResponse response = client.query( Notes: -- The reader is constructed only when the request format is `JSONEachRow`. - The default request format remains `RowBinaryWithNamesAndTypes`, and - callers that do not explicitly opt in are not affected. -- `client.newTextFormatReader(response)` returns a `ClickHouseTextFormatReader` - for text output formats. `client.newBinaryFormatReader(response)` continues - to return a `ClickHouseBinaryFormatReader` for binary output formats and - rejects text formats (such as `JSONEachRow`) with - `IllegalArgumentException`. Callers that need to handle both can program - against the shared `ClickHouseFormatReader` parent interface. +- Set `ClickHouseFormat.JSONEachRow` in `QuerySettings`. Do not rely on an SQL + `FORMAT JSONEachRow` clause for direct `client-v2` examples, because the + client applies JSON-specific server settings only when the request settings + identify the format as `JSONEachRow`. +- `client.newBinaryFormatReader(response)` continues to return a + `ClickHouseBinaryFormatReader` for binary output formats and rejects text + formats such as `JSONEachRow` with `IllegalArgumentException`. Callers that + need to handle both can program against the shared `ClickHouseFormatReader` + parent interface. - `Map` is the canonical materialization for JSON columns and for the row itself, as produced by the selected library. JSON arrays are returned as `List`; nested JSON objects are returned as nested @@ -270,9 +261,14 @@ format on the caller's behalf. Properties props = new Properties(); props.setProperty("user", "default"); props.setProperty("password", ""); -props.setProperty(ClientConfigProperties.JSON_PROCESSOR.getKey(), "JACKSON"); // or "GSON" +props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), + JacksonJsonParserFactory.class.getName()); // The JSON column type is experimental on the server side. props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); try (Connection conn = DriverManager.getConnection( "jdbc:clickhouse://localhost:8123/default", props); @@ -302,8 +298,11 @@ Behavior: selected JSON parser (`Map`, `List`, or scalar), without an additional string round-trip. - The JSON processor is selected at the connection level through the - `json_processor` option. It cannot be changed per statement, in line with - the lifecycle of other client options. + `jdbc_json_parser_factor` driver property. It cannot be changed per + statement, in line with the lifecycle of other connection options. +- Because JDBC selects `JSONEachRow` through SQL text, set the JSON output + server settings explicitly as connection properties when integer or decimal + numeric accessors are used. ## Forced server settings for `JSONEachRow` @@ -320,8 +319,10 @@ request: | `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. | These overrides are scoped to the individual request and apply only when the -request format is `JSONEachRow`. They are required for the typed accessors of -the reader to operate correctly and must not be overridden by callers. +request format in `QuerySettings` is `JSONEachRow`. They are required for the +typed accessors of the reader to operate correctly. JDBC callers that use SQL +`FORMAT JSONEachRow` should set the same server settings explicitly through +connection properties. ## Row parsing, schema, and typed accessors @@ -345,6 +346,48 @@ JSON-to-Java decisions are governed entirely by the library. The reader neither inspects raw JSON tokens nor overrides the library's parsing behavior. +### Integer precision with Gson + +ClickHouse `Int64` and `UInt64` values can exceed the exactly representable +integer range of a JSON floating-point number. The client intentionally emits +them as JSON numbers for `JSONEachRow`, so the selected JSON library's number +materialization policy matters. + +Jackson's default `Map.class` materialization keeps ordinary integer tokens as +integer `Number` implementations. Gson's default `Map` +materialization can surface JSON numbers as floating-point values, which may +round integers larger than `2^53` before `getLong(...)` sees them. + +If you use Gson and need integer precision, provide a custom factory that +configures Gson's object number strategy: + +```java +public final class PreciseGsonJsonParserFactory extends GsonJsonParserFactory { + @Override + protected void customize(GsonBuilder builder) { + builder.setObjectToNumberStrategy(com.google.gson.ToNumberPolicy.LONG_OR_DOUBLE); + } +} +``` + +Use that factory directly with `client-v2`: + +```java +JsonParserFactory parserFactory = new PreciseGsonJsonParserFactory(); +``` + +For JDBC, put the factory class name in the connection properties: + +```java +props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), + PreciseGsonJsonParserFactory.class.getName()); +``` + +`ToNumberPolicy.LONG_OR_DOUBLE` preserves values that fit in `long` as +`Long`. If exact decimal representation is more important than returning +`Long` for integer tokens, use `ToNumberPolicy.BIG_DECIMAL` and convert +explicitly at the application boundary. + ### Minimal schema discovery `JSONEachRow` does not include a schema header. To populate a minimal @@ -408,7 +451,7 @@ For these types, callers should obtain the parsed value through ## Streaming and lifetime -- `JacksonJsonParser` and `GsonJsonParser` delegate parsing to the +- `JacksonJsonParserFactory` and `GsonJsonParserFactory` delegate parsing to the underlying library and consume one row at a time from the response `InputStream`. Memory consumption is proportional to the size of the current row and is independent of the size of the result set. @@ -422,8 +465,9 @@ For these types, callers should obtain the parsed value through ## Compatibility considerations -- The `JSON_PROCESSOR` property is additive. Applications that do not request - `JSONEachRow` and do not set this property are unaffected. +- The parser factory classes are additive. Applications that do not request + `JSONEachRow` and do not instantiate or configure a parser factory are + unaffected. - The default request format is unchanged. The existing binary readers (`Native`, `RowBinary`, `RowBinaryWithNames`, `RowBinaryWithNamesAndTypes`) retain their previous behavior. @@ -431,32 +475,28 @@ For these types, callers should obtain the parsed value through `ClickHouseBinaryFormatReader` and `ClickHouseTextFormatReader` are sibling sub-interfaces of the new `ClickHouseFormatReader`. The accessor surface is unchanged; callers that hold a `ClickHouseBinaryFormatReader` reference for - binary formats are unaffected. Callers that previously obtained a - `ClickHouseBinaryFormatReader` for `JSONEachRow` from - `Client.newBinaryFormatReader(...)` must switch to - `Client.newTextFormatReader(...)`, which returns a - `ClickHouseTextFormatReader`. `Client.newBinaryFormatReader(...)` now - rejects `JSONEachRow` with `IllegalArgumentException`. + binary formats are unaffected. `Client.newBinaryFormatReader(...)` rejects + `JSONEachRow` with `IllegalArgumentException`; construct + `JSONEachRowFormatReader` directly for JSONEachRow streams. - Jackson and Gson are now declared with `provided` scope in `client-v2` and `jdbc-v2`. Applications that previously inherited Jackson transitively from these modules in `test` scope must declare the chosen processor explicitly on their runtime classpath. -- The previously undocumented `json_processor` driver property has been - removed from `DriverProperties`. The same value is now configured as a - regular client option; the runtime behavior is unchanged. +- `jdbc_json_parser_factor` is a new JDBC driver property and is only needed + by connections that execute `FORMAT JSONEachRow` queries. ## Examples Two runnable Gradle examples are provided under `examples/`: - `examples/client-v2-json-processors` exercises the `client-v2` API - directly, switching between `JACKSON` and `GSON` against a shared sample + directly, switching between Jackson and Gson factories against a shared sample table that contains primitive columns and one `payload JSON` column. Entry point: `ClientV2JsonProcessorsExample`. - `examples/jdbc-v2-json-processors` performs the same flow through the JDBC driver, with `FORMAT JSONEachRow` appended to the `SELECT` statement and - the same `JACKSON` / `GSON` selection applied through connection - properties. Entry point: `JdbcV2JsonProcessorsExample`. + parser factory selection applied through connection properties. Entry point: + `JdbcV2JsonProcessorsExample`. Both examples include a sample dataset under `src/main/resources/sample_data.csv` and require a running ClickHouse server @@ -469,7 +509,7 @@ with `allow_experimental_json_type=1`. the subclasses `JacksonJSONEachRowFormatReaderTests` and `GsonJSONEachRowFormatReaderTests`. The suite covers basic parsing, schema inference, primitive type accessors, and empty result sets. -- `jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java` adds the - test methods `testJSONEachRowFormat` and `testJSONEachRowFormatGson`, - which exercise `Statement.executeQuery("... FORMAT JSONEachRow")` through - the JDBC driver against both processors. +- `jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java` adds + `testJSONEachRowFormat`, which exercises + `Statement.executeQuery("... FORMAT JSONEachRow")` through the JDBC driver + against both parser factories. diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index 097ea6f47..45db8c60c 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -43,6 +43,36 @@ Each read call in `run()` follows the same three-step shape: response stream through `new JSONEachRowFormatReader(factory.createJsonParser(...))`. +The client example selects the output format with +`new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)`. Use that form +instead of appending `FORMAT JSONEachRow` to the SQL when calling `client-v2` +directly, because the client applies JSON-specific server settings from the +request format. + +## Integer Precision + +ClickHouse 64-bit integers can be larger than the exact integer range of a +JSON floating-point number. Jackson's default map materialization preserves +ordinary integer tokens as integer `Number` values. Gson's default +`Map` materialization may surface numbers as floating-point +values, which can round large integers before `getLong(...)` sees them. + +For Gson, extend `GsonJsonParserFactory` and configure the object number +strategy: + +```java +public final class PreciseGsonJsonParserFactory extends GsonJsonParserFactory { + @Override + protected void customize(GsonBuilder builder) { + builder.setObjectToNumberStrategy(com.google.gson.ToNumberPolicy.LONG_OR_DOUBLE); + } +} +``` + +The included `CustomGsonParserFactory` uses this pattern. Use +`ToNumberPolicy.BIG_DECIMAL` instead when exact decimal representation matters +more than receiving integer tokens as `Long`. + ## Requirements - JDK 17 or newer diff --git a/examples/jdbc-dispatcher-demo/.gitignore b/examples/jdbc-dispatcher-demo/.gitignore new file mode 100644 index 000000000..67bcc2f72 --- /dev/null +++ b/examples/jdbc-dispatcher-demo/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build/ diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock b/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/checksums.lock deleted file mode 100644 index 35e148177bf2a0218ce98a957287f455a676e5b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZRcQT}jGUfG_L3=p6R06${|LI3~& diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin b/examples/jdbc-dispatcher-demo/.gradle/8.8/checksums/md5-checksums.bin deleted file mode 100644 index 5ee85113546b3e11b96654bff97b50e7762450bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18897 zcmeI(?@JSL0LSs0u%R|=62`!Fr%QuqbsC z#9$E74;3^F%x8@VZO{W{f(;_72PK5Y@Ih1%*`43}BDw!S-UoO0<#XT9-F;tAkMk&s z-L`3L%J!DGJ!FIc0tg_000IagfB*srAb9F?e_wIb2F%9%q|8U-_oiam zu0zKB2QtsC%Zz;PZ+U9ul`^ki`g)^#z#1~{b5-V-mrtkuq^s?)$H#l|u^oxd?l4GK= z+#nid<>`&prdUDye&5kDhtCsl{B>S5ez%Io9{o3m9dGq#EjZlM;atDd*mGgg>YUqZ zMBFyfFzbum(Fk`H`#s%-)xOxx&YXDX53LbZzfGr%XB4J496v*CUsgQx&HW?(wp8yv m(Rk(+jq<V+Xj5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SNpr0!8$L1d@jl z#Vuh5D|8g42%-nV^L^row}%Qw$lV$&O(1ScXPSB-})|kVs8`4vz=W2 zwc>M6#H{QRo2$4yps9Lj+SThKc}^*phaW2)tM$3v$ev%v<#J=$ExXOnmpxy`<*Jtx z)!O=?vn0>bak)ChH>18Ke~i7(6qg?v+$1%1haO~eF_%{w#6CB#j?3BWq;om9rzjsW zIfg9+AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<;NKKTq^Gr!USpUw%q)4+@V(#Y^jl?`xw5iKT&r{n=wHV-OQ(jr z$o!XXq4x=Yw|4K_erJtyz*BQkx!lkerHwnhtBh{xQ}sPo(u%ytZ;k#;6#Sa|6ldD-uDI~2~HE9W%zdbCPVcJvA zV@c|7??MLk!WIu{h9%Z;JL*}yHP!HW|mVPv8 znU=5STpYi(>|tf2J+I;9{PB`DMR8=;_0!j6q}5KhnMD{yZx+pP`b*z8Q$lM}XJ3iq zNvf&n#QXIhNUI~;TcVQZ`K0+t0RI$i4j4{MP8^%T*qIMQ(Yy-7PZi57x;w^!s7%W!vg) J31iGm>kkn3ICB61 diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties b/examples/jdbc-dispatcher-demo/.gradle/8.8/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin b/examples/jdbc-dispatcher-demo/.gradle/8.8/executionHistory/executionHistory.bin deleted file mode 100644 index f3eb3dbb161fd0890222768304f323159e36ff61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45038 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 V#EzD7$APaeJ6B)j|Kwm&egp45Bdq`c diff --git a/examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties b/examples/jdbc-dispatcher-demo/.gradle/8.8/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/examples/jdbc-dispatcher-demo/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 380783ace6c0ebad386d0399f9ef25a22d280b78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZQ(-TdwS~dyF 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�=+ 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�HGYXzg-E^{{i;= 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 diff --git a/examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip b/examples/jdbc-dispatcher-demo/build/distributions/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.zip deleted file mode 100644 index af28c826f4673716555cb4b8a3454c8524a0cf96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143537 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ĭ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%0D*8(M-L&hNF>}%V9v;epzp<57GIL+ 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 B#GU{E diff --git a/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties b/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties deleted file mode 100644 index 1f90e6a60..000000000 --- a/examples/jdbc-dispatcher-demo/build/resources/main/simplelogger.properties +++ /dev/null @@ -1,18 +0,0 @@ -# SLF4J SimpleLogger configuration - -# Default log level for all loggers -org.slf4j.simpleLogger.defaultLogLevel=info - -# Log level for specific loggers -org.slf4j.simpleLogger.log.com.clickhouse.examples.dispatcher=info -org.slf4j.simpleLogger.log.com.clickhouse.jdbc.dispatcher=debug - -# Show date/time in log output -org.slf4j.simpleLogger.showDateTime=true -org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss.SSS - -# Show logger name (short form) -org.slf4j.simpleLogger.showShortLogName=true - -# Show thread name -org.slf4j.simpleLogger.showThreadName=false diff --git a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo deleted file mode 100755 index 2c8571ca6..000000000 --- a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# jdbc-dispatcher-demo start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh jdbc-dispatcher-demo -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and JDBC_DISPATCHER_DEMO_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}.." > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/lib/jdbc-dispatcher-demo-1.0.0-SNAPSHOT.jar:$APP_HOME/lib/jdbc-dispatcher-0.9.6-SNAPSHOT.jar:$APP_HOME/lib/slf4j-simple-2.0.16.jar:$APP_HOME/lib/slf4j-api-2.0.16.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and JDBC_DISPATCHER_DEMO_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and JDBC_DISPATCHER_DEMO_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - -classpath "$CLASSPATH" \ - com.clickhouse.examples.dispatcher.DispatcherDemo \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $JDBC_DISPATCHER_DEMO_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat b/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat deleted file mode 100644 index 50e608383..000000000 --- a/examples/jdbc-dispatcher-demo/build/scripts/jdbc-dispatcher-demo.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem jdbc-dispatcher-demo startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME%.. - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and JDBC_DISPATCHER_DEMO_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\lib\jdbc-dispatcher-demo-1.0.0-SNAPSHOT.jar;%APP_HOME%\lib\jdbc-dispatcher-0.9.6-SNAPSHOT.jar;%APP_HOME%\lib\slf4j-simple-2.0.16.jar;%APP_HOME%\lib\slf4j-api-2.0.16.jar - - -@rem Execute jdbc-dispatcher-demo -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %JDBC_DISPATCHER_DEMO_OPTS% -classpath "%CLASSPATH%" com.clickhouse.examples.dispatcher.DispatcherDemo %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable JDBC_DISPATCHER_DEMO_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%JDBC_DISPATCHER_DEMO_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherDemo.class.uniqueId5 deleted file mode 100644 index 9f669e95ceecd1d93169d3f64784e01243ea145f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10873 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 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$DriversHandler.class.uniqueId1 deleted file mode 100644 index c933b93c89cef9e9f68ee8da8e7cc137f4544f80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3465 zcmbVO>30)V6#u<8?WE~Y(g4AtU@Zl@1OkdE4OF2N+S)})Ev8s?nmn3m)0r?cDVzJg z;f^9MxPluB9texVaXY9SJx9NK{NUf}@niR|FNiunJ-@E(o-uo_GJaZ1f zT6|f80u)Mc$S6WFLsM8E_J?C?cqpnTOz!79mEm}doBoJu#uY0Z<%WO5*fPkC5jD)K zHyG*&H_R4Ai^RCWP;5n2vwAths`lx~3%KA!i3FF7QpgPML1jeo#}sYA-_<|J!xm{+ zqiU+v$WT~Q+v7wTEjBfGyWmE-1dohan9Z?H?UXxY3o93_FA zqz`awGvA)zrq#i%s2*XMT~pgWt}|#Esy0AIc`;YQ6*4L?Z-PwK=|yNhu+p>Sg$_l)*KQ9flRT zh!~9YhbKpujVg+}AIl}IkZ~=pWANvMXDWV%a!POy!Bf?>4n4SPc~hTRT6HHaU)hUG)!L(x{ej3xjV8_a}tVi4KC0Pe7PCsj3A_wTv+E{54CTU<=W@R&G3 zOp6l@LYEQ8cJgh|)U`|(TvD?&FFJuq^$@aHpFnLw#t3%UU9Xexpr$uw^YW={VnQZU z9dVa%a5ux^?9^piUcPZo+$#X3Yh0lEei;u4haIN6n|o*=JtX5{JVO0vSQ+9?x)xTf zEvglzm1!xOMP97V){}-vzM9E`Eu#3CjK@Wfb&6)$Mv;{8Btwj3eBi=|I3(e)K=Vfo z)#K)=+K4{H{WfrIfNxa7)Vp^v?9YaF`k*b;2BBHquIodIcpxuS*^~*4-MivEdox`y z*&q!IY=JYJwZl2srP2Wg=C zol&2}%p~RXcRlgQJ2KBBvzhA16>##6^_%m znc;QlVzrUs8?-eP%|8H_&(oen=a#}opWt&l&%zrj^lT1>92G@LbO%F4o-M&pv3Loc z!kxj8AZG+;k*I7n6*#rgp8zCm_wLh-zd z;?BL0Nm%zUDkZf2gYHpcEs{if-S$=+Xx}?Z(sRgk#7keiNx~eo{T4kB(z*ce;N8^! f0=!560+R33?_+#QZywq&peghjt&iXf0$jy^)yCl$ diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$HealthHandler.class.uniqueId0 deleted file mode 100644 index 93ba4967870dbf9ae0f3d48270513b78f285a041..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1494 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 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService$VersionHandler.class.uniqueId2 deleted file mode 100644 index 5b3fd2b3d11d1274fcbc70362efff957fed76286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3552 zcmbVP+jARN8UGzw(kk*Mw$;Rm(p*I2G`3?~O`DWf%7w;pY8TmQBIDwgV%Do;Yi)T~ z*^5;WaOPDg2IBlI06&rVrXZ z=R4o|uD|d5R)72G{XYXZfp;{71df`{LfR}@=6unqcrq;)jfHYadg+4Yl?~r4N;iF` zvCK>Nl4Z&*2qU5)szXCeVDOX3=$eqOXF2w;VHZl$6^Qyp%j>#F;Qrhvk(Z4lj#dr2 zj;+`x&^~8eGSVf(o=u;do|C3e4aY6p@=pr1boWfe(9XrfL*sEIaEFHNI(DE_;DA!@ zRqV7a{dCdy%N~7Z5Qe!NS~QCkC@Fnb`WNKIiuC+Z=@*@X!1nH*+^WvJ?^^aOjoO8z zhTS^u#GW-q1q?KbQjC%(u%o-_dSG(4=kXZ!2_ym?DckW=MycdnmW4P3QaV0`y9Bm) z(k=|SuEQv1yQ|Ur7zeT=O}c8P_DpP{%q|@VakoHxHU9!f)@Nj{Fl}y*kTb5uXk}yQ z7U))NnN@IQ(&>_88l|G+`I!ff+YK%422b2JuQ@*tzTX7sGG@R6N3ZEfn8b&lE3+QrDniXH3uSoX_6SRrnO&(s5 z9+Q)oJ{>%bK@Deg4B>3E>;{WK*p`cQinT@14wGp$@l!P~Vt9mMuWgjfoQ_e95y32{ zdO_U%|Bb3vb1gqHJSx!I=|=XZZ%G+8P{2;rzpkN;9gNKPU-lP>PHc?_K$v6xYbV$qyY&8mne5G8bTu-vI>}rlk<3_e;LZy64fp1E~96QKr z%FpN6t7KVKkt(i|js@6EPRW^NW^#@@oAydGN9WQxXLhz)uVsO4XAIw%HawY1#66Xp^P@(&7TkCP!_56l^}4Z~^vJo!2~m`HDy}KdS}Jax8@^QrR7hyf7)SLm z2M>XK+Hri(ca3r|R=u_;ei6s3cum9W3WINO46X*!vM)LFlKoi?^Z=G;4byj21AVy} z_%+5rXn;kpE9W@#m2!4tOKGC4xj25MEH`0q?o;)TgN-5v`E`)De90N|D}j!+^_)ow zPJj_+&>bupY%Rpawuv=gVH$oTaBzd~o4KUnw*vca%}PKA{v*XNM%6^W2GQQGz8YEY zA^x`FX;Poz>LmAy67N0yN3`_*30vOfE`+c186^+!JiftaXTXK4_$GJcuSz2t@hqO> z*)zPf!W$YOP9)k6FQa1_J6}OZ?|W!D+#y~}>|MtGch&Aa97r4r7Cp=8AMIPgeF9_s zi3eAZ4IvXwhF9>Az%`62uJ-5$*frUbcsM`Flf=jhJ{v-QGLkr#pNx`lu13%j=kt@X zL_WWa&t)QkTKqK`S;piN+Jn8gnfL#O)RevCKq z0^TBJiJGrb-x4)m!%Zwv(>0OAJJhfw_7Z;Gcv~FD@2apErnOfIr*Glgh*0_u@Ev@Y zpgV@^_#VDbiw5u?Qh&%lJ$tbqKcXhWO`NC$iL2-F6T;{P+PsL4-5+65!%+>#HJsMR zG#LLyJE9uG8WN2Ik|FqreTY!5%Ck-VGiv5p&#+aUHW-3a?L&yu8m#?{7HG9##KQO) k*CD)2@6}r{a$w;IMo-|Ej1~CLB~;~plk2w_skf2*A4jO*ZU6uP diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/compileTransaction/stash-dir/DispatcherService.class.uniqueId4 deleted file mode 100644 index 4a9a971b58a0daa26152f9e6e61ffe9499ce5b72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10339 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 diff --git a/examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin b/examples/jdbc-dispatcher-demo/build/tmp/compileJava/previous-compilation-data.bin deleted file mode 100644 index b2fd138014ac1ae12ce735d705fc39e53a6da63b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1974 zcmZ`)eNdxL9*Y50(-9O&@yzl#a ze$U7Iyw4j2YE(+nrN#(lBppS+LXV@zgU`&{CqSp!#+mD^yvNN(Lp$5R*qt`kJrO!B zoZHC=W-IHG!Ml^7R;fM#HR)O zVEy6#^_{6Ra`RGs|&qR^0IDJpG#bys2fUYEc6#@!O? zYmaJn4~1q(A1A02mr0Vq?_phyG1Rr)7q`}*nAM^BmAv?=amw$d)B@oS(|`P}&p$Cs z2C{o6)g>zm?Khi7d-I~0m667#U2nv`s!ml;r_<;e^lS9^nc#DK9O>Y5 zu)+_Z(<%r~HwlWw`a1Z?BnBY@V&f7(`kjzbm^usE8q8M4QNtoiUvAG|B%-dz}m@2fD^jccMkR*9ohajth&1|C?{EW@RTENF}E^2V=vqZcxr&(*8m~Z~w zAEJw?0`IkNu3s)r=M(K;-#>lls+OvFFXv|Fsq*QImN3Gjq-PsgQ?;Q+VWl}*Q44KW z*5)Lk$m+mvWzBUqVodP39A;`Tsp8}aE1{M}NLJ4=JgBzo97Zb-9iA$;zzH6KRZ+L| z&RT!Hap79Su8b*9{n8UodA-7=bn6A$Lo2+b1f86WeCr@+VVnZFJ=;?IUO4ZKJeu>E zN#F3Nn^*Ky!7mS9Kf4~;2iG?ru%5oKszF!TNUFfg+Nk>22VDE$>Knt2#{AL?d%tKh zt~V^oFQC?Xr+vl0$T78cjf7?_xZU5pbVK1rov;alq#`&+O$&lw%-6dJK@Vpm2SI7J z8A^kDOeLf9e5rlq@9%9z)n?TDP_tC#M+h*kI@YD5dUJN-;fHa|nPpWMqmNKS0W1un zu5wcgQj)5q0=&=ecId@}r z_VefWCysq9r;e7tQaHpFXhg@Wz%9~Duf8-}Me%viHS6PNf|N8{p;+OVu0jgHA3 z2yHIj%Zbh&dHf_dYGbb?dFt(+%L6x#bRwuw=!N2ZXU_zlcvkk{@?I=` zAJe;0whf`3)C?nD{XPU=qm!ksesw`X<;S60{+N$X`O(p+{WkM(7t2ZKx&g?j>TB+#bT_Rxz-BB0>u*$XE=A$Rtt> zivWo>kedY{`5Dqj&^Uz3qnLFJvxYHi1hd9Zh$e5s@NY$9d``, runs the `SELECT ... FORMAT JSONEachRow` and iterates the `ResultSet`. +Because JDBC selects `JSONEachRow` through SQL text, set the JSON output +server settings explicitly on the connection when numeric accessors are used: + +```java +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); +props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); +``` + +## Integer Precision + +ClickHouse 64-bit integers can be larger than the exact integer range of a +JSON floating-point number. Jackson's default map materialization preserves +ordinary integer tokens as integer `Number` values. Gson's default +`Map` materialization may surface numbers as floating-point +values, which can round large integers before `ResultSet.getLong(...)` sees +them. + +For Gson, extend `GsonJsonParserFactory` and configure the object number +strategy: + +```java +public final class PreciseGsonJsonParserFactory extends GsonJsonParserFactory { + @Override + protected void customize(GsonBuilder builder) { + builder.setObjectToNumberStrategy(com.google.gson.ToNumberPolicy.LONG_OR_DOUBLE); + } +} +``` + +Then configure JDBC with the factory class name: + +```java +props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), + PreciseGsonJsonParserFactory.class.getName()); +``` + +The included `CustomGsonParserFactory` uses this pattern. Use +`ToNumberPolicy.BIG_DECIMAL` instead when exact decimal representation matters +more than receiving integer tokens as `Long`. + ## Requirements - JDK 17 or newer diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index cbbf25e36..4424f296d 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -187,6 +187,10 @@ private Properties baseProperties() { properties.setProperty("user", user); properties.setProperty("password", password); properties.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); + properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); + properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); + properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); + properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); return properties; } From 4eb1cfe4f2cbe441dfd14760c27c210b105154b1 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 13:00:15 -0700 Subject: [PATCH 18/26] Fixed documentation and typo --- docs/client-v2-json-support.md | 8 ++++---- docs/features.md | 5 ++++- examples/jdbc-v2-json-processors/README.md | 2 +- .../json_processors/JdbcV2JsonProcessorsExample.java | 4 ++-- .../main/java/com/clickhouse/jdbc/DriverProperties.java | 8 ++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 0d8cee085..65350c277 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -78,7 +78,7 @@ provides additional advantages beyond what the format alone delivers: - Modifies `StatementImpl.executeQuery(...)` to accept `JSONEachRow` as a valid output format. All other text formats remain unsupported. - Adds `DriverProperties.JSON_PARSER_FACTORY` - (key `jdbc_json_parser_factor`) for selecting the `JsonParserFactory` + (key `jdbc_json_parser_factory`) for selecting the `JsonParserFactory` implementation by fully-qualified class name. - Declares Jackson and Gson as `provided` dependencies, consistent with `client-v2`. @@ -172,7 +172,7 @@ as `Map`. | Property key | Value | | ------------------------- | ----- | -| `jdbc_json_parser_factor` | Fully-qualified class name implementing `JsonParserFactory` | +| `jdbc_json_parser_factory` | Fully-qualified class name implementing `JsonParserFactory` | The named class is loaded reflectively when the connection is created and must have a public no-argument constructor. There is no equivalent `client-v2` @@ -298,7 +298,7 @@ Behavior: selected JSON parser (`Map`, `List`, or scalar), without an additional string round-trip. - The JSON processor is selected at the connection level through the - `jdbc_json_parser_factor` driver property. It cannot be changed per + `jdbc_json_parser_factory` driver property. It cannot be changed per statement, in line with the lifecycle of other connection options. - Because JDBC selects `JSONEachRow` through SQL text, set the JSON output server settings explicitly as connection properties when integer or decimal @@ -482,7 +482,7 @@ For these types, callers should obtain the parsed value through `jdbc-v2`. Applications that previously inherited Jackson transitively from these modules in `test` scope must declare the chosen processor explicitly on their runtime classpath. -- `jdbc_json_parser_factor` is a new JDBC driver property and is only needed +- `jdbc_json_parser_factory` is a new JDBC driver property and is only needed by connections that execute `FORMAT JSONEachRow` queries. ## Examples diff --git a/docs/features.md b/docs/features.md index 2e7bf1ef6..3e2bc445f 100644 --- a/docs/features.md +++ b/docs/features.md @@ -14,6 +14,7 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t - Parameterized SQL: Accepts named query parameters and can send them through supported HTTP request encodings. - Result materialization helpers: Provides streaming `Records`, generic row access, and convenience APIs that materialize all rows into generic records or typed POJOs. - Binary format readers: Reads ClickHouse binary result formats including `Native`, `RowBinary`, `RowBinaryWithNames`, and `RowBinaryWithNamesAndTypes`. +- JSONEachRow text reader: Can stream `JSONEachRow` responses through a caller-supplied `JsonParser`, with Jackson and Gson parser factory implementations available as optional classpath dependencies. - Data type conversion: Maps ClickHouse types to Java values for binary reads, POJO binding, and SQL parameter formatting, including date/time handling. - Geometry type support: For ClickHouse `25.11+`, where `Geometry` changed from a string alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)`, the client reads and writes `Geometry` values through generic records, binary readers, POJO binding, and SQL parameter formatting, using Java array dimensionality to represent the geometry shape. - Insert APIs: Supports inserting registered POJOs, raw streams, and callback-driven writers, with optional column lists and format selection. @@ -39,6 +40,7 @@ Compatibility-sensitive traits: - `Geometry` handling is shape-sensitive: supported values are 1D through 4D Java arrays representing the nested geometry variants, and unsupported shapes or non-array values are rejected during serialization. - `Geometry` write inference is dimension-based rather than fully type-specific: point, ring/line string, polygon/multi-line string, and multi-polygon are selected from array depth, so writing `Geometry` cannot currently distinguish `Ring` from `LineString` or `Polygon` from `MultiLineString`. - Session precedence is part of the contract: client session defaults apply to each request, operation settings may override them, and only the client `session_id` is mutable at runtime while other client session properties remain fixed for the lifetime of the client. +- JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON-specific server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow`. ## `jdbc-v2` @@ -57,7 +59,7 @@ Compatibility-sensitive traits: - Prepared statements: Supports `?` parameters through client-side SQL rendering and validates that all parameters are bound before execution. - SQL parsing and classification: Classifies SQL to distinguish queries, updates, inserts, `USE`, and role-changing statements, with selectable parser backends. - JDBC escape processing: Translates supported JDBC escape syntax for dates, timestamps, and functions before execution. -- Result set streaming: Streams result sets from ClickHouse binary formats, enforces max-row limits, and manages result-set lifecycle correctly. +- Result set streaming: Streams result sets from ClickHouse binary formats and `FORMAT JSONEachRow`, enforces max-row limits, and manages result-set lifecycle correctly. - Result-set metadata: Exposes JDBC `ResultSetMetaData` backed by ClickHouse column schema. - Database metadata: Implements JDBC `DatabaseMetaData` for ClickHouse catalogs, schemas, tables, columns, and related capability reporting. - Parameter metadata: Reports prepared-statement parameter counts. @@ -77,6 +79,7 @@ Compatibility-sensitive traits: - String-like ClickHouse values have stable JDBC expectations: `String`, `FixedString`, and `Enum` values are returned as strings, while `UUID` is available both as `getString()` and `getObject(..., UUID.class)`. - `Geometry` has a stable JDBC mapping: metadata reports SQL type `ARRAY` with type name `Geometry`, read paths return nested Java arrays rather than custom wrappers, and write paths depend on the caller preserving the intended point/array nesting shape. - JDBC `Geometry` writes share the same ambiguity as the client serializer: variant selection is inferred from nesting depth, so `Ring` versus `LineString` and `Polygon` versus `MultiLineString` are not currently distinguishable when writing through the generic `Geometry` path. +- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. - Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression. - Stream and reader setters (`setAsciiStream`, `setUnicodeStream`, `setBinaryStream`, `setCharacterStream`, `setNCharacterStream`) are treated as text input encoded with the same string-escaping rules, including length-based truncation when a length is supplied. - `getString()` formatting for temporal values is stable output: `Date` uses `yyyy-MM-dd`, `DateTime` uses `yyyy-MM-dd HH:mm:ss`, and `DateTime64` preserves fractional precision, all interpreted in server timezone context where applicable. diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md index f36f3ca48..820bab0d0 100644 --- a/examples/jdbc-v2-json-processors/README.md +++ b/examples/jdbc-v2-json-processors/README.md @@ -12,7 +12,7 @@ through `jdbc-v2` with the two factories shipped under ### How the JDBC driver selects a factory The driver picks the parser factory **by fully-qualified class name** from -the `JSON_PARSER_FACTORY` driver property (key: `jdbc_json_parser_factor`). +the `jdbc_json_parser_factory` driver property. The value is the FQN of a class that implements `JsonParserFactory`; the driver loads it reflectively and instantiates it through a **public no-arg constructor**. There is no enum-style selector. diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index 4424f296d..6a6026235 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -209,7 +209,7 @@ private static String sqlString(String value) { * *

This class must be {@code public static} with a public no-arg * constructor because the JDBC driver loads it reflectively via the - * {@code jdbc_json_parser_factor} driver property; the {@code .getName()} + * {@code jdbc_json_parser_factory} driver property; the {@code .getName()} * of a nested class is the {@code Outer$Inner} binary form, which * {@code Class.forName(...)} accepts.

* @@ -246,7 +246,7 @@ public Payload toPayload(Object rawPayload) { * *

This class must be {@code public static} with a public no-arg * constructor because the JDBC driver loads it reflectively via the - * {@code jdbc_json_parser_factor} driver property; the {@code .getName()} + * {@code jdbc_json_parser_factory} driver property; the {@code .getName()} * of a nested class is the {@code Outer$Inner} binary form, which * {@code Class.forName(...)} accepts.

* diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index a6185983c..7beedf50b 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -130,11 +130,11 @@ public enum DriverProperties { CLUSTER_NAME("jdbc_cluster_name", null), /** - * Defines what {@link com.clickhouse.client.api.data_formats.JsonParserFactory} implementation connection - * should use when response is in {@code JSONEachRow} format. Value is the name of the factory class. Driver - * will + * Defines which {@link com.clickhouse.client.api.data_formats.JsonParserFactory} implementation the connection + * should use when the response is in {@code JSONEachRow} format. Value is the fully-qualified class name of + * the factory class. */ - JSON_PARSER_FACTORY("jdbc_json_parser_factor", null), + JSON_PARSER_FACTORY("jdbc_json_parser_factory", null), ; From a640112488fb012c684d163819c19921c55c349e Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 13:24:40 -0700 Subject: [PATCH 19/26] fixed problem with next() == true forever --- .../data_formats/JSONEachRowFormatReader.java | 38 +++++++++++-------- .../JSONEachRowFormatReaderTest.java | 6 +-- .../clickhouse/jdbc/ResultSetImplTest.java | 24 ++++++++++++ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index 142adb488..806286b31 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -30,19 +30,20 @@ public class JSONEachRowFormatReader implements ClickHouseTextFormatReader { private final JsonParser parser; private TableSchema schema; private Map currentRow; - private Map firstRow; - private boolean firstRowRead = false; + private Map nextRow; + private boolean hasNext; public JSONEachRowFormatReader(JsonParser parser) { this.parser = parser; try { - this.firstRow = parser.nextRow(); - if (firstRow != null) { + this.nextRow = parser.nextRow(); + this.hasNext = this.nextRow != null; + if (nextRow != null) { List columns = new ArrayList<>(); - for (String key : firstRow.keySet()) { + for (String key : nextRow.keySet()) { // For JSONEachRow we don't know the exact ClickHouse type, so we use a reasonable default. // We can try to guess based on the value type in the first row. - columns.add(ClickHouseColumn.of(key, guessDataType(firstRow.get(key)), false)); + columns.add(ClickHouseColumn.of(key, guessDataType(nextRow.get(key)), false)); } this.schema = new TableSchema(columns); } else { @@ -95,23 +96,28 @@ public boolean hasValue(int colIndex) { @Override public boolean hasNext() { - if (!firstRowRead) { - return firstRow != null; - } - return true; // We'll find out in next() + return hasNext; } @Override public Map next() { - if (!firstRowRead) { - firstRowRead = true; - currentRow = firstRow; - return currentRow; + if (!hasNext) { + currentRow = null; + return null; } + + currentRow = nextRow; + readNextRow(); + return currentRow; + } + + private void readNextRow() { try { - currentRow = parser.nextRow(); - return currentRow; + nextRow = parser.nextRow(); + hasNext = nextRow != null; } catch (Exception e) { + hasNext = false; + nextRow = null; throw new RuntimeException("Failed to read next JSON row", e); } } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index 8aaacdeb8..749645047 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -203,8 +203,6 @@ public Map nextRow() { public void close() { } }; try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader(parser)) { - // First row was read eagerly during construction. - Assert.assertNotNull(reader.next()); try { reader.next(); Assert.fail("Expected RuntimeException"); @@ -227,11 +225,9 @@ public void testHasNextAndNext() throws Exception { try (JSONEachRowFormatReader reader = readerOf(r1, r2)) { Assert.assertTrue(reader.hasNext()); Assert.assertSame(reader.next(), r1); - // After the first row has been returned, hasNext() optimistically - // returns true; callers detect the end of the stream when next() - // returns null. Assert.assertTrue(reader.hasNext()); Assert.assertSame(reader.next(), r2); + Assert.assertFalse(reader.hasNext()); Assert.assertNull(reader.next()); } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java index 4c3262dd6..2fd8fb53e 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java @@ -1,6 +1,7 @@ package com.clickhouse.jdbc; import com.clickhouse.client.api.ClientConfigProperties; +import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory; import com.clickhouse.data.ClickHouseVersion; import org.testng.Assert; import org.testng.annotations.Test; @@ -264,6 +265,29 @@ public void testCursorPosition() throws SQLException { } } + @Test(groups = {"integration"}) + public void testJsonEachRowCursorPositionDetectsLastRow() throws SQLException { + Properties properties = new Properties(); + properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), JacksonJsonParserFactory.class.getName()); + properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow"); + try (Connection conn = getJdbcConnection(properties); Statement stmt = conn.createStatement()) { + int limit = 13; + try (ResultSet rs = stmt.executeQuery("SELECT number FROM system.numbers LIMIT " + limit)) { + + for (int i = 0; i < limit - 1; i++) { + Assert.assertTrue(rs.next()); + Assert.assertFalse(rs.isLast()); + } + + Assert.assertTrue(rs.next()); + Assert.assertTrue(rs.isLast()); + + Assert.assertFalse(rs.next()); + Assert.assertFalse(rs.isLast()); + } + } + } + @Test(groups = {"integration"}) public void testFetchDirectionsAndSize() throws SQLException { From c31679b00a1da6d46b32448c4d9451e825f8bdeb Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 14:34:12 -0700 Subject: [PATCH 20/26] Fixed type inference for JSON --- .../clickhouse/data/ClickHouseDataType.java | 3 +- .../data_formats/JSONEachRowFormatReader.java | 24 +---- .../client/api/internal/SchemaUtils.java | 87 +++++++++++++++++++ .../AbstractJSONEachRowFormatReaderTests.java | 8 +- .../JSONEachRowFormatReaderTest.java | 55 +++++++----- docs/features.md | 3 +- 6 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 client-v2/src/main/java/com/clickhouse/client/api/internal/SchemaUtils.java diff --git a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java index c93963fa1..5a9ec06c7 100644 --- a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java +++ b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java @@ -177,7 +177,8 @@ public static Map, Integer> buildVariantMapping(List>> DATA_TYPE_TO_CLASS = dataTypeClassMap(); + public static final Map>> DATA_TYPE_TO_CLASS = + Collections.unmodifiableMap(dataTypeClassMap()); static Map>> dataTypeClassMap() { Map>> map = new HashMap<>(); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index 806286b31..d1137a42f 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -1,8 +1,8 @@ package com.clickhouse.client.api.data_formats; +import com.clickhouse.client.api.internal.SchemaUtils; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.value.ClickHouseBitmap; import com.clickhouse.data.value.ClickHouseGeoMultiPolygonValue; import com.clickhouse.data.value.ClickHouseGeoPointValue; @@ -43,7 +43,7 @@ public JSONEachRowFormatReader(JsonParser parser) { for (String key : nextRow.keySet()) { // For JSONEachRow we don't know the exact ClickHouse type, so we use a reasonable default. // We can try to guess based on the value type in the first row. - columns.add(ClickHouseColumn.of(key, guessDataType(nextRow.get(key)), false)); + columns.add(ClickHouseColumn.of(key, SchemaUtils.inferDataType(nextRow.get(key)), false)); } this.schema = new TableSchema(columns); } else { @@ -54,26 +54,6 @@ public JSONEachRowFormatReader(JsonParser parser) { } } - private ClickHouseDataType guessDataType(Object value) { - if (value instanceof Number) { - if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) { - return ClickHouseDataType.Int64; - } else if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) { - double d = ((Number) value).doubleValue(); - if (d == Math.floor(d) && !Double.isInfinite(d) && d <= Long.MAX_VALUE && d >= Long.MIN_VALUE) { - return ClickHouseDataType.Int64; - } - return ClickHouseDataType.Float64; - } else { - return ClickHouseDataType.Float64; - } - } else if (value instanceof Boolean) { - return ClickHouseDataType.Bool; - } else { - return ClickHouseDataType.String; - } - } - @Override public T readValue(int colIndex) { return (T) currentRow.get(schema.columnIndexToName(colIndex)); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/SchemaUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/SchemaUtils.java new file mode 100644 index 000000000..ed0ce67b6 --- /dev/null +++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/SchemaUtils.java @@ -0,0 +1,87 @@ +package com.clickhouse.client.api.internal; + +import com.clickhouse.data.ClickHouseDataType; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class SchemaUtils { + private static final ClickHouseDataType DEFAULT_DATA_TYPE = ClickHouseDataType.String; + + private static final List DATA_TYPE_PRIORITY = ImmutableList.of( + ClickHouseDataType.Bool, + ClickHouseDataType.Int8, + ClickHouseDataType.Int16, + ClickHouseDataType.Int32, + ClickHouseDataType.Int64, + ClickHouseDataType.Int256, + ClickHouseDataType.Float32, + ClickHouseDataType.Float64, + ClickHouseDataType.Decimal, + ClickHouseDataType.String, + ClickHouseDataType.UUID, + ClickHouseDataType.IPv4, + ClickHouseDataType.IPv6, + ClickHouseDataType.DateTime64, + ClickHouseDataType.Date, + ClickHouseDataType.IntervalNanosecond, + ClickHouseDataType.IntervalDay, + ClickHouseDataType.Time64, + ClickHouseDataType.Point, + ClickHouseDataType.Ring, + ClickHouseDataType.Polygon, + ClickHouseDataType.MultiPolygon, + ClickHouseDataType.Tuple, + ClickHouseDataType.Geometry + ); + + private static final Map, ClickHouseDataType> CLASS_TO_DATA_TYPE = buildClassToDataTypeMap(); + + private SchemaUtils() { + } + + public static ClickHouseDataType inferDataType(Object value) { + if (value == null) { + return DEFAULT_DATA_TYPE; + } + + // JSONEachRow has no type metadata, so structural values only infer the + // top-level family; nested key/value/element types remain best-effort. + if (value instanceof Map) { + return ClickHouseDataType.Map; + } + + Class valueClass = ClickHouseDataType.toObjectType(value.getClass()); + if (value instanceof List || valueClass.isArray()) { + return ClickHouseDataType.Array; + } + + ClickHouseDataType dataType = CLASS_TO_DATA_TYPE.get(valueClass); + return dataType == null ? DEFAULT_DATA_TYPE : dataType; + } + + private static Map, ClickHouseDataType> buildClassToDataTypeMap() { + Map, ClickHouseDataType> map = new LinkedHashMap<>(); + + for (ClickHouseDataType dataType : DATA_TYPE_PRIORITY) { + addTypeMappings(map, dataType); + } + + return ImmutableMap.copyOf(map); + } + + private static void addTypeMappings(Map, ClickHouseDataType> map, ClickHouseDataType dataType) { + Set> javaClasses = ClickHouseDataType.DATA_TYPE_TO_CLASS.get(dataType); + if (javaClasses == null) { + return; + } + + for (Class javaClass : javaClasses) { + map.putIfAbsent(ClickHouseDataType.toObjectType(javaClass), dataType); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 2f37977a5..c2076c211 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -165,8 +165,8 @@ private static String buildValuesClause(List> rows) { @Test(groups = {"integration"}) public void testSchemaInference() throws Exception { - // Covers all branches of guessDataType: numeric (Int64), numeric (Float64), - // Boolean (Bool) and the catch-all branch that maps strings to String. + // Numeric inference depends on parser materialization, so this test checks + // that numerics do not collapse to String and stable scalar types still map. String sql = "SELECT toInt64(42) as col_int, toFloat64(3.14) as col_float, " + "true as col_bool, 'val' as col_str"; @@ -176,8 +176,8 @@ public void testSchemaInference() throws Exception { Assert.assertNotNull(reader.getSchema()); Assert.assertEquals(reader.getSchema().getColumns().size(), 4); - Assert.assertEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.Int64); - Assert.assertEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.Float64); + Assert.assertNotEquals(reader.getSchema().getColumnByIndex(1).getDataType(), ClickHouseDataType.String); + Assert.assertNotEquals(reader.getSchema().getColumnByIndex(2).getDataType(), ClickHouseDataType.String); Assert.assertEquals(reader.getSchema().getColumnByIndex(3).getDataType(), ClickHouseDataType.Bool); Assert.assertEquals(reader.getSchema().getColumnByIndex(4).getDataType(), ClickHouseDataType.String); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index 749645047..7dc11c4f9 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -6,6 +6,8 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,26 +59,26 @@ private static JSONEachRowFormatReader readerOf(Map... rows) { } // --------------------------------------------------------------------- - // guessDataType + // Schema inference // --------------------------------------------------------------------- @Test - public void testGuessDataTypeForIntegerLikeValuesIsInt64() { + public void testSchemaInferenceForIntegerLikeValues() { JSONEachRowFormatReader reader = readerOf(row( "as_integer", 1, "as_long", 2L, "as_big_integer", BigInteger.TEN)); Assert.assertEquals(reader.getSchema().getColumnByName("as_integer").getDataType(), - ClickHouseDataType.Int64); + ClickHouseDataType.Int32); Assert.assertEquals(reader.getSchema().getColumnByName("as_long").getDataType(), ClickHouseDataType.Int64); Assert.assertEquals(reader.getSchema().getColumnByName("as_big_integer").getDataType(), - ClickHouseDataType.Int64); + ClickHouseDataType.Int256); } @Test - public void testGuessDataTypeForFractionalDoubleIsFloat64() { + public void testSchemaInferenceForFractionalValues() { JSONEachRowFormatReader reader = readerOf(row( "as_double", 1.5d, "as_float", 2.5f, @@ -85,24 +87,24 @@ public void testGuessDataTypeForFractionalDoubleIsFloat64() { Assert.assertEquals(reader.getSchema().getColumnByName("as_double").getDataType(), ClickHouseDataType.Float64); Assert.assertEquals(reader.getSchema().getColumnByName("as_float").getDataType(), - ClickHouseDataType.Float64); + ClickHouseDataType.Float32); Assert.assertEquals(reader.getSchema().getColumnByName("as_big_decimal").getDataType(), - ClickHouseDataType.Float64); + ClickHouseDataType.Decimal); } @Test - public void testGuessDataTypeForWholeDoubleIsInt64() { + public void testSchemaInferenceUsesJavaTypeForWholeFractionalValues() { JSONEachRowFormatReader reader = readerOf(row( "as_double_whole", 5.0d, "as_float_whole", 7.0f, "as_big_decimal_whole", new BigDecimal("42"))); Assert.assertEquals(reader.getSchema().getColumnByName("as_double_whole").getDataType(), - ClickHouseDataType.Int64); + ClickHouseDataType.Float64); Assert.assertEquals(reader.getSchema().getColumnByName("as_float_whole").getDataType(), - ClickHouseDataType.Int64); + ClickHouseDataType.Float32); Assert.assertEquals(reader.getSchema().getColumnByName("as_big_decimal_whole").getDataType(), - ClickHouseDataType.Int64); + ClickHouseDataType.Decimal); } @Test @@ -121,38 +123,47 @@ public void testGuessDataTypeForOutOfRangeDoubleIsFloat64() { } @Test - public void testGuessDataTypeForOtherNumberSubtypesIsFloat64() { - // AtomicInteger is a Number that is neither Integer/Long/BigInteger - // nor Double/Float/BigDecimal, so it lands in the catch-all numeric - // branch. + public void testSchemaInferenceForUnsupportedNumberSubtypesUsesDefault() { + // AtomicInteger is a Number, but it is not part of ClickHouseDataType.DATA_TYPE_TO_CLASS. JSONEachRowFormatReader reader = readerOf(row("custom", new AtomicInteger(5))); Assert.assertEquals(reader.getSchema().getColumnByName("custom").getDataType(), - ClickHouseDataType.Float64); + ClickHouseDataType.String); } @Test - public void testGuessDataTypeForBooleanIsBool() { + public void testSchemaInferenceForBooleanIsBool() { JSONEachRowFormatReader reader = readerOf(row("flag", Boolean.TRUE)); Assert.assertEquals(reader.getSchema().getColumnByName("flag").getDataType(), ClickHouseDataType.Bool); } @Test - public void testGuessDataTypeDefaultBranchIsString() { - // Strings, lists, maps, and JSON null should all fall through to the - // catch-all branch and be reported as String columns. + public void testSchemaInferenceForStructuredAndSpecialValues() { + UUID uuid = UUID.fromString("11111111-2222-3333-4444-555555555555"); JSONEachRowFormatReader reader = readerOf(row( "as_string", "hello", "as_list", Arrays.asList(1, 2, 3), + "as_array", new double[] {1.0d, 2.0d}, "as_map", Collections.singletonMap("k", "v"), + "as_uuid", uuid, + "as_date", LocalDate.of(2024, 1, 2), + "as_datetime", LocalDateTime.of(2024, 1, 2, 3, 4), "as_null", null)); Assert.assertEquals(reader.getSchema().getColumnByName("as_string").getDataType(), ClickHouseDataType.String); Assert.assertEquals(reader.getSchema().getColumnByName("as_list").getDataType(), - ClickHouseDataType.String); + ClickHouseDataType.Array); + Assert.assertEquals(reader.getSchema().getColumnByName("as_array").getDataType(), + ClickHouseDataType.Array); Assert.assertEquals(reader.getSchema().getColumnByName("as_map").getDataType(), - ClickHouseDataType.String); + ClickHouseDataType.Map); + Assert.assertEquals(reader.getSchema().getColumnByName("as_uuid").getDataType(), + ClickHouseDataType.UUID); + Assert.assertEquals(reader.getSchema().getColumnByName("as_date").getDataType(), + ClickHouseDataType.Date); + Assert.assertEquals(reader.getSchema().getColumnByName("as_datetime").getDataType(), + ClickHouseDataType.DateTime64); Assert.assertEquals(reader.getSchema().getColumnByName("as_null").getDataType(), ClickHouseDataType.String); } diff --git a/docs/features.md b/docs/features.md index 3e2bc445f..072132822 100644 --- a/docs/features.md +++ b/docs/features.md @@ -14,7 +14,7 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t - Parameterized SQL: Accepts named query parameters and can send them through supported HTTP request encodings. - Result materialization helpers: Provides streaming `Records`, generic row access, and convenience APIs that materialize all rows into generic records or typed POJOs. - Binary format readers: Reads ClickHouse binary result formats including `Native`, `RowBinary`, `RowBinaryWithNames`, and `RowBinaryWithNamesAndTypes`. -- JSONEachRow text reader: Can stream `JSONEachRow` responses through a caller-supplied `JsonParser`, with Jackson and Gson parser factory implementations available as optional classpath dependencies. +- JSONEachRow text reader: Can stream `JSONEachRow` responses through a caller-supplied `JsonParser`, with Jackson and Gson parser factory implementations available as optional classpath dependencies, and infers a best-effort schema from the first row. - Data type conversion: Maps ClickHouse types to Java values for binary reads, POJO binding, and SQL parameter formatting, including date/time handling. - Geometry type support: For ClickHouse `25.11+`, where `Geometry` changed from a string alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)`, the client reads and writes `Geometry` values through generic records, binary readers, POJO binding, and SQL parameter formatting, using Java array dimensionality to represent the geometry shape. - Insert APIs: Supports inserting registered POJOs, raw streams, and callback-driven writers, with optional column lists and format selection. @@ -41,6 +41,7 @@ Compatibility-sensitive traits: - `Geometry` write inference is dimension-based rather than fully type-specific: point, ring/line string, polygon/multi-line string, and multi-polygon are selected from array depth, so writing `Geometry` cannot currently distinguish `Ring` from `LineString` or `Polygon` from `MultiLineString`. - Session precedence is part of the contract: client session defaults apply to each request, operation settings may override them, and only the client `session_id` is mutable at runtime while other client session properties remain fixed for the lifetime of the client. - JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON-specific server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow`. +- JSONEachRow schema inference is intentionally best-effort: scalar values use Java-to-ClickHouse type mappings, while JSON arrays and objects are identified structurally as `Array` and `Map`. For arrays, maps, and some nested or ambiguous values, the inferred type may not include the most specific element, key, value, or nested ClickHouse type. ## `jdbc-v2` From a6e969cea27f2f38f69b361cfb08298d7654af37 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 15:38:54 -0700 Subject: [PATCH 21/26] Fixed NPE and mapping --- .../data_formats/JSONEachRowFormatReader.java | 1 + docs/client-v2-json-support.md | 26 ++++++++++++++----- .../com/clickhouse/jdbc/StatementImpl.java | 6 +++++ .../com/clickhouse/jdbc/StatementTest.java | 14 ++++++++++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index d1137a42f..4094d1402 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -167,6 +167,7 @@ public BigInteger getBigInteger(String colName) { @Override public BigDecimal getBigDecimal(String colName) { Object val = currentRow.get(colName); + if (val == null) return null; if (val instanceof BigDecimal) return (BigDecimal) val; return new BigDecimal(val.toString()); } diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 65350c277..274cf0b73 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -294,9 +294,9 @@ Behavior: and `JSONEachRow` as output formats; any other text format causes `SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported for output format. ...")` to be thrown. -- `ResultSet.getObject(...)` returns the value produced directly by the - selected JSON parser (`Map`, `List`, or scalar), without an additional - string round-trip. +- `ResultSet.getObject(...)` returns parser-native `Map` and scalar values + without an additional string round-trip. JSON arrays are exposed as JDBC + `Array` values, wrapping the parser-produced list. - The JSON processor is selected at the connection level through the `jdbc_json_parser_factory` driver property. It cannot be changed per statement, in line with the lifecycle of other connection options. @@ -397,11 +397,16 @@ maps each to a `ClickHouseDataType`: | Java type produced by the library | Inferred ClickHouse type | | ---------------------------------------------------------------------- | ------------------------ | -| `Integer`, `Long`, `BigInteger` | `Int64` | -| `Double`, `Float`, `BigDecimal` whose value has no fractional part within the `long` range | `Int64` | -| Other `Number` subtypes | `Float64` | +| `Integer` | `Int32` | +| `Long` | `Int64` | +| `BigInteger` | `Int256` | +| `Float` | `Float32` | +| `Double` | `Float64` | +| `BigDecimal` | `Decimal` | | `Boolean` | `Bool` | -| Any other value (`String`, `List`, `Map`, `null`, ...) | `String` | +| `List` or Java array | `Array` | +| `Map` | `Map` | +| Any other value (`String`, `null`, unsupported number subtypes, ...) | `String` | Implications: @@ -435,6 +440,13 @@ follows: | `getTuple` | Returns the row value cast to `Object[]`. | | `getEnum8` / `getEnum16` | Delegates to `getByte` / `getShort`. | +Accessor limitations to keep in mind: + +- `getTuple(...)` does not adapt parser-native `List` or `Map` values. Since + JSON arrays are usually materialized as `List` and JSON objects as `Map`, + callers should use `readValue(...)` for tuple-like JSON values and convert + them explicitly. + The following accessors are not supported by the current implementation and throw `UnsupportedOperationException`: diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index a9df54662..471582b0d 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -181,6 +181,12 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr ClickHouseFormatReader reader; if (response.getFormat() == ClickHouseFormat.JSONEachRow) { + if (connection.getJsonParserFactory() == null) { + throw new SQLException("Response is in JSONEachRow format, but " + + DriverProperties.JSON_PARSER_FACTORY.getKey() + " is not configured. Set " + + DriverProperties.JSON_PARSER_FACTORY.getKey() + " to a JsonParserFactory implementation.", + ExceptionUtils.SQL_STATE_CLIENT_ERROR); + } reader = new JSONEachRowFormatReader(connection.getJsonParserFactory().createJsonParser(response.getInputStream())); } else if (!response.getFormat().isText()) { reader = connection.getClient().newBinaryFormatReader(response); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index a01994e3b..347102372 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -737,6 +737,20 @@ public void testJSONEachRowFormat(Class parserFactory) throws } } + @Test(groups = {"integration"}) + public void testJSONEachRowFormatRequiresParserFactory() throws Exception { + try (Connection conn = getJdbcConnection(); + Statement stmt = conn.createStatement()) { + try { + stmt.executeQuery("SELECT 1 AS num FORMAT JSONEachRow"); + fail("Expected SQLException"); + } catch (SQLException e) { + assertTrue(e.getMessage().contains(DriverProperties.JSON_PARSER_FACTORY.getKey()), + "Unexpected message: " + e.getMessage()); + } + } + } + @DataProvider public static Object[][] testJSONEachRowFormatDP() { return new Object[][] { From aaffb6f33b9602e3955fb18facb88c7b659a6d4c Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 16:00:17 -0700 Subject: [PATCH 22/26] Fixed breaking change and documented more --- .../com/clickhouse/client/api/Client.java | 13 ++-- .../client/api/ClientConfigProperties.java | 5 ++ .../com/clickhouse/client/ClientTests.java | 6 +- .../clickhouse/client/query/QueryTests.java | 23 ++++++ docs/client-v2-json-support.md | 78 ++++++++++++------- docs/features.md | 4 +- examples/client-v2-json-processors/README.md | 4 +- .../ClientV2JsonProcessorsExample.java | 5 +- examples/jdbc-v2-json-processors/README.md | 5 +- .../JdbcV2JsonProcessorsExample.java | 1 - .../com/clickhouse/jdbc/ResultSetImpl.java | 19 ++++- .../clickhouse/jdbc/ResultSetImplTest.java | 22 ++++++ 12 files changed, 139 insertions(+), 46 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index c95b51c72..a71ed7481 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -3,7 +3,6 @@ import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.command.CommandSettings; import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; -import com.clickhouse.client.api.data_formats.JsonParserFactory; import com.clickhouse.client.api.data_formats.NativeFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryFormatReader; import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; @@ -2223,21 +2222,21 @@ private Map buildRequestSettings(Map opSettings) * Must be called after {@link #buildRequestSettings(Map)} and after the request format has been resolved * (either provided by the caller or defaulted), so that the inspected format reflects the final value. * - *

For {@link ClickHouseFormat#JSONEachRow} the JSON output flags below are forced to {@code 0} so that the - * stream contains plain JSON numbers (and not quoted strings or non-standard tokens), which is what - * {@link com.clickhouse.client.api.data_formats.JSONEachRowFormatReader} expects:

+ *

For {@link ClickHouseFormat#JSONEachRow}, callers may opt in to plain JSON numbers by setting + * {@link ClientConfigProperties#JSON_DISABLE_NUMBER_QUOTING}. Explicit server settings are otherwise + * left untouched.

*
    *
  • {@code output_format_json_quote_64bit_integers}
  • *
  • {@code output_format_json_quote_64bit_floats}
  • - *
  • {@code output_format_json_quote_denormals}
  • *
  • {@code output_format_json_quote_decimals}
  • *
*/ private static void applyFormatSpecificSettings(QuerySettings requestSettings) { - if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow) { + boolean disableNumberQuoting = ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING + .getOrDefault(requestSettings.getAllSettings()); + if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow && disableNumberQuoting) { requestSettings.serverSetting("output_format_json_quote_64bit_integers", "0"); requestSettings.serverSetting("output_format_json_quote_64bit_floats", "0"); - requestSettings.serverSetting("output_format_json_quote_denormals", "0"); requestSettings.serverSetting("output_format_json_quote_decimals", "0"); } } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java index e548a90f9..5a61fcebf 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java @@ -190,6 +190,11 @@ public Object parseValue(String value) { */ HTTP_SEND_PARAMS_IN_BODY("client.http.use_form_request_for_query", Boolean.class, "false"), + /** + * When enabled for JSONEachRow queries, asks ClickHouse to emit large integer, + * floating-point, and decimal values as JSON numbers instead of quoted strings. + */ + JSON_DISABLE_NUMBER_QUOTING("json_disable_number_quoting", Boolean.class, "false"), /** * Prefix for custom settings. Should be aligned with server configuration. diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 0508d9e58..07154058c 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -329,7 +329,7 @@ public void testDefaultSettings() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. } try (Client client = new Client.Builder() @@ -362,7 +362,7 @@ public void testDefaultSettings() { .setSocketSndbuf(100000) .build()) { Map config = client.getConfiguration(); - Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 36); // to check everything is set. Increment when new added. Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb"); Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10"); Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000"); @@ -429,7 +429,7 @@ public void testWithOldDefaults() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added. } } 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 2651f88e9..2f81d2eb5 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 @@ -7,6 +7,7 @@ import com.clickhouse.client.ClickHouseProtocol; import com.clickhouse.client.ClickHouseServerForTest; import com.clickhouse.client.api.Client; +import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.ClientException; import com.clickhouse.client.api.ServerException; import com.clickhouse.client.api.command.CommandSettings; @@ -380,6 +381,28 @@ public void testQueryJSONEachRow() throws ExecutionException, InterruptedExcepti } } + @Test(groups = {"integration"}) + public void testJsonEachRowNumberQuoteSettingsAreOptIn() throws Exception { + String sql = "SELECT toInt64(1234567890123) AS v"; + + QuerySettings settings = new QuerySettings() + .setFormat(ClickHouseFormat.JSONEachRow) + .serverSetting("output_format_json_quote_64bit_integers", "1"); + try (QueryResponse response = client.query(sql, settings).get(); + BufferedReader reader = new BufferedReader(new InputStreamReader(response.getInputStream()))) { + Assert.assertTrue(reader.readLine().contains("\"v\":\"1234567890123\"")); + } + + QuerySettings unquotedSettings = new QuerySettings() + .setFormat(ClickHouseFormat.JSONEachRow) + .serverSetting("output_format_json_quote_64bit_integers", "1") + .setOption(ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING.getKey(), true); + try (QueryResponse response = client.query(sql, unquotedSettings).get(); + BufferedReader reader = new BufferedReader(new InputStreamReader(response.getInputStream()))) { + Assert.assertTrue(reader.readLine().contains("\"v\":1234567890123")); + } + } + @DataProvider(name = "rowBinaryFormats") ClickHouseFormat[] getRowBinaryFormats() { return new ClickHouseFormat[]{ diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index 274cf0b73..cc72f19e8 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -67,9 +67,9 @@ provides additional advantages beyond what the format alone delivers: `com.clickhouse.client.api.data_formats`, consisting of `JsonParser`, `JsonParserFactory`, `JacksonJsonParserFactory`, and `GsonJsonParserFactory`. -- Forces a fixed set of server settings for `JSONEachRow` requests so that - the response stream contains plain JSON numbers (see - [Forced server settings](#forced-server-settings-for-jsoneachrow)). +- Adds an opt-in client flag for `JSONEachRow` requests that asks ClickHouse + to emit large integers, floats, and decimals as plain JSON numbers (see + [JSON number output settings](#json-number-output-settings)). - Declares Jackson and Gson as `provided` Maven dependencies, so that applications must include the chosen processor on their own classpath. @@ -179,6 +179,18 @@ have a public no-argument constructor. There is no equivalent `client-v2` configuration key; direct client users pass a factory instance to their own reader construction code. +### JSON number quoting flag + +`client-v2` can opt in to numeric JSON output settings through +`ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING`: + +| Property key | Default | Effect | +| --------------------------------------------- | ------- | ------ | +| `json_disable_number_quoting` | `false` | When `true` and the resolved request format is `JSONEachRow`, sets `output_format_json_quote_64bit_integers=0`, `output_format_json_quote_64bit_floats=0`, and `output_format_json_quote_decimals=0` for that request. | + +The flag can be set on the client builder or on a specific `QuerySettings` +instance. It does not change `output_format_json_quote_denormals`. + ## Runtime dependencies `client-v2` and `jdbc-v2` declare the JSON libraries with `provided` scope, @@ -216,7 +228,8 @@ Client client = new Client.Builder() JsonParserFactory parserFactory = new JacksonJsonParserFactory(); QuerySettings settings = new QuerySettings() - .setFormat(ClickHouseFormat.JSONEachRow); + .setFormat(ClickHouseFormat.JSONEachRow) + .setOption(ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING.getKey(), true); try (QueryResponse response = client.query( "SELECT id, name, active, score, payload FROM events ORDER BY id", @@ -237,9 +250,9 @@ try (QueryResponse response = client.query( Notes: - Set `ClickHouseFormat.JSONEachRow` in `QuerySettings`. Do not rely on an SQL - `FORMAT JSONEachRow` clause for direct `client-v2` examples, because the - client applies JSON-specific server settings only when the request settings - identify the format as `JSONEachRow`. + `FORMAT JSONEachRow` clause for direct `client-v2` examples when you also + want client-side JSON number output settings, because those settings are + applied only when the request settings identify the format as `JSONEachRow`. - `client.newBinaryFormatReader(response)` continues to return a `ClickHouseBinaryFormatReader` for binary output formats and rejects text formats such as `JSONEachRow` with `IllegalArgumentException`. Callers that @@ -267,7 +280,6 @@ props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), props.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); -props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); try (Connection conn = DriverManager.getConnection( @@ -294,9 +306,11 @@ Behavior: and `JSONEachRow` as output formats; any other text format causes `SQLException("Only RowBinaryWithNameAndTypes and JSONEachRow are supported for output format. ...")` to be thrown. -- `ResultSet.getObject(...)` returns parser-native `Map` and scalar values - without an additional string round-trip. JSON arrays are exposed as JDBC - `Array` values, wrapping the parser-produced list. +- `ResultSet.getObject(...)` returns parser-native `Map`, `List`, and scalar + values without an additional string round-trip. JSON arrays are returned as + the `List` implementation produced by the selected JSON library. Because + `JSONEachRow` has no array element metadata, `ResultSet.getArray(...)` is + not supported for these inferred JSON arrays. - The JSON processor is selected at the connection level through the `jdbc_json_parser_factory` driver property. It cannot be changed per statement, in line with the lifecycle of other connection options. @@ -304,25 +318,34 @@ Behavior: server settings explicitly as connection properties when integer or decimal numeric accessors are used. -## Forced server settings for `JSONEachRow` +## JSON number output settings `Client.applyFormatSpecificSettings(...)` runs after request settings have been merged and after the request format has been resolved. When the format -is `JSONEachRow`, the following server-side settings are forced for the -request: +is `JSONEachRow` and +`ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING` is enabled, the +following server-side settings are set to `0` for the request: -| Setting | Forced value | Rationale | +| Setting | Value | Rationale | | ----------------------------------------- | ------------ | -------------------------------------------------------------------------- | -| `output_format_json_quote_64bit_integers` | `0` | Emits `Int64` and `UInt64` as JSON numbers rather than quoted strings. | -| `output_format_json_quote_64bit_floats` | `0` | Emits 64-bit floating-point values as JSON numbers. | -| `output_format_json_quote_denormals` | `0` | Avoids quoting `NaN` and `Inf`, allowing materialization as `Double`. | -| `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. | +| `output_format_json_quote_64bit_integers` | `0` | Emits `Int64` and `UInt64` as JSON numbers rather than quoted strings. | +| `output_format_json_quote_64bit_floats` | `0` | Emits 64-bit floating-point values as JSON numbers. | +| `output_format_json_quote_decimals` | `0` | Emits decimals as JSON numbers, allowing materialization as `BigDecimal` or `Double`. | + +These overrides are scoped to the individual request and apply only when both +conditions are true: the request format in `QuerySettings` is `JSONEachRow`, +and `json_disable_number_quoting` is enabled through the client +or request settings. Explicit server settings are otherwise preserved. + +Denormal floating-point values (`NaN`, `Inf`, `-Inf`) are not yet handled by +the built-in JSON reader. The client does not set +`output_format_json_quote_denormals`; keep the server default or set +`output_format_json_quote_denormals=1` so these values are quoted, then handle +them as strings at the application boundary. -These overrides are scoped to the individual request and apply only when the -request format in `QuerySettings` is `JSONEachRow`. They are required for the -typed accessors of the reader to operate correctly. JDBC callers that use SQL -`FORMAT JSONEachRow` should set the same server settings explicitly through -connection properties. +JDBC callers that use SQL `FORMAT JSONEachRow` should set the same numeric +server settings explicitly through connection properties when integer or +decimal numeric accessors are used. ## Row parsing, schema, and typed accessors @@ -349,9 +372,10 @@ behavior. ### Integer precision with Gson ClickHouse `Int64` and `UInt64` values can exceed the exactly representable -integer range of a JSON floating-point number. The client intentionally emits -them as JSON numbers for `JSONEachRow`, so the selected JSON library's number -materialization policy matters. +integer range of a JSON floating-point number. When +`json_disable_number_quoting` is enabled, the client asks +ClickHouse to emit them as JSON numbers for `JSONEachRow`, so the selected +JSON library's number materialization policy matters. Jackson's default `Map.class` materialization keeps ordinary integer tokens as integer `Number` implementations. Gson's default `Map` diff --git a/docs/features.md b/docs/features.md index 072132822..cbb723620 100644 --- a/docs/features.md +++ b/docs/features.md @@ -40,7 +40,7 @@ Compatibility-sensitive traits: - `Geometry` handling is shape-sensitive: supported values are 1D through 4D Java arrays representing the nested geometry variants, and unsupported shapes or non-array values are rejected during serialization. - `Geometry` write inference is dimension-based rather than fully type-specific: point, ring/line string, polygon/multi-line string, and multi-polygon are selected from array depth, so writing `Geometry` cannot currently distinguish `Ring` from `LineString` or `Polygon` from `MultiLineString`. - Session precedence is part of the contract: client session defaults apply to each request, operation settings may override them, and only the client `session_id` is mutable at runtime while other client session properties remain fixed for the lifetime of the client. -- JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON-specific server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow`. +- JSONEachRow reading depends on the selected parser factory and request format settings: parser materialization determines Java value types, the reader infers minimal schema from the first row, and JSON number server settings are applied only when `QuerySettings` resolves to `ClickHouseFormat.JSONEachRow` and `json_disable_number_quoting` is enabled. - JSONEachRow schema inference is intentionally best-effort: scalar values use Java-to-ClickHouse type mappings, while JSON arrays and objects are identified structurally as `Array` and `Map`. For arrays, maps, and some nested or ambiguous values, the inferred type may not include the most specific element, key, value, or nested ClickHouse type. @@ -80,7 +80,7 @@ Compatibility-sensitive traits: - String-like ClickHouse values have stable JDBC expectations: `String`, `FixedString`, and `Enum` values are returned as strings, while `UUID` is available both as `getString()` and `getObject(..., UUID.class)`. - `Geometry` has a stable JDBC mapping: metadata reports SQL type `ARRAY` with type name `Geometry`, read paths return nested Java arrays rather than custom wrappers, and write paths depend on the caller preserving the intended point/array nesting shape. - JDBC `Geometry` writes share the same ambiguity as the client serializer: variant selection is inferred from nesting depth, so `Ring` versus `LineString` and `Polygon` versus `MultiLineString` are not currently distinguishable when writing through the generic `Geometry` path. -- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. +- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. - Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression. - Stream and reader setters (`setAsciiStream`, `setUnicodeStream`, `setBinaryStream`, `setCharacterStream`, `setNCharacterStream`) are treated as text input encoded with the same string-escaping rules, including length-based truncation when a length is supplied. - `getString()` formatting for temporal values is stable output: `Date` uses `yyyy-MM-dd`, `DateTime` uses `yyyy-MM-dd HH:mm:ss`, and `DateTime64` preserves fractional precision, all interpreted in server timezone context where applicable. diff --git a/examples/client-v2-json-processors/README.md b/examples/client-v2-json-processors/README.md index 45db8c60c..ff8360664 100644 --- a/examples/client-v2-json-processors/README.md +++ b/examples/client-v2-json-processors/README.md @@ -46,8 +46,8 @@ Each read call in `run()` follows the same three-step shape: The client example selects the output format with `new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow)`. Use that form instead of appending `FORMAT JSONEachRow` to the SQL when calling `client-v2` -directly, because the client applies JSON-specific server settings from the -request format. +directly when you enable client-side JSON number output settings, because +those settings depend on the request format. ## Integer Precision diff --git a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java index 736796b0c..6951bb6e2 100644 --- a/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java +++ b/examples/client-v2-json-processors/src/main/java/com/clickhouse/examples/client_v2/json_processors/ClientV2JsonProcessorsExample.java @@ -1,6 +1,7 @@ package com.clickhouse.examples.client_v2.json_processors; import com.clickhouse.client.api.Client; +import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.command.CommandResponse; import com.clickhouse.client.api.data_formats.ClickHouseTextFormatReader; import com.clickhouse.client.api.data_formats.GsonJsonParserFactory; @@ -96,7 +97,9 @@ public void run() throws Exception { public void readAll(String label, JsonParserFactory factory) throws Exception { LOG.info("--- Reading rows with {} ---", label); - QuerySettings settings = new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow); + QuerySettings settings = new QuerySettings() + .setFormat(ClickHouseFormat.JSONEachRow) + .setOption(ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING.getKey(), true); String sql = "SELECT id, name, active, score, payload FROM " + TABLE + " ORDER BY id"; PayloadConverter converter = factory instanceof PayloadConverter ? (PayloadConverter) factory : null; diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md index 820bab0d0..80f03b24f 100644 --- a/examples/jdbc-v2-json-processors/README.md +++ b/examples/jdbc-v2-json-processors/README.md @@ -90,10 +90,13 @@ server settings explicitly on the connection when numeric accessors are used: ```java props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); -props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); ``` +Denormal floating-point values (`NaN`, `Inf`, `-Inf`) are not handled by the +built-in JSON reader yet. Keep `output_format_json_quote_denormals=1` and +handle those values as strings if your queries can return them. + ## Integer Precision ClickHouse 64-bit integers can be larger than the exact integer range of a diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java index 6a6026235..bccb90c92 100644 --- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java +++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java @@ -189,7 +189,6 @@ private Properties baseProperties() { properties.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); - properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_denormals"), "0"); properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); return properties; } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index ce3a71773..4a84842a2 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -44,6 +44,7 @@ import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -1495,8 +1496,14 @@ public T getObjectImpl(String columnLabel, Class type, Map T getObjectImpl(String columnLabel, Class type, Map) { + throw new SQLException("JSONEachRow arrays are returned as parser-native List values. " + + "Use getObject(...) to read this column."); } - return (T) JdbcUtils.convert(reader.readValue(columnLabel), type, column); + return (T) JdbcUtils.convert(value, type, column); } else { wasNull = true; return null; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java index 2fd8fb53e..f5e28c6dd 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java @@ -26,6 +26,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.util.List; import java.util.Properties; import static org.testng.Assert.assertEquals; @@ -288,6 +289,27 @@ public void testJsonEachRowCursorPositionDetectsLastRow() throws SQLException { } } + @Test(groups = {"integration"}) + public void testJsonEachRowGetObjectReturnsParserNativeArray() throws SQLException { + Properties properties = new Properties(); + properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), JacksonJsonParserFactory.class.getName()); + try (Connection conn = getJdbcConnection(properties); Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT [1, 2, 3] AS arr FORMAT JSONEachRow")) { + Assert.assertTrue(rs.next()); + Object value = rs.getObject("arr"); + Assert.assertTrue(value instanceof List, "Expected parser-native List but got " + value.getClass()); + + List list = (List) value; + Assert.assertEquals(list.size(), 3); + Assert.assertEquals(((Number) list.get(0)).intValue(), 1); + Assert.assertEquals(((Number) list.get(1)).intValue(), 2); + Assert.assertEquals(((Number) list.get(2)).intValue(), 3); + + Assert.expectThrows(SQLException.class, () -> rs.getArray("arr")); + } + } + } + @Test(groups = {"integration"}) public void testFetchDirectionsAndSize() throws SQLException { From 4d4fc7b6253bcf8b0a6f46df3ba287d1d61a4b83 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 21 May 2026 16:21:34 -0700 Subject: [PATCH 23/26] Fixed tuples --- .../client/api/data_formats/JSONEachRowFormatReader.java | 9 ++++++++- .../api/data_formats/JSONEachRowFormatReaderTest.java | 6 +++++- docs/client-v2-json-support.md | 7 +++++++ docs/features.md | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index 4094d1402..b01e6f3fc 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -429,7 +429,14 @@ public Object[] getTuple(int index) { @Override public Object[] getTuple(String colName) { - return (Object[]) currentRow.get(colName); + Object value = currentRow.get(colName); + if (value == null) { + return null; + } + if (value instanceof List) { + return ((List) value).toArray(new Object[0]); + } + return (Object[]) value; } @Override diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index 7dc11c4f9..7cbb22651 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -401,7 +401,8 @@ public void testUuidAndListAccessors() throws Exception { UUID uuid = UUID.fromString("11111111-2222-3333-4444-555555555555"); try (JSONEachRowFormatReader reader = readerOf(row( "u", uuid.toString(), - "arr", Arrays.asList(1, 2, 3)))) { + "arr", Arrays.asList(1, 2, 3), + "tuple", Arrays.asList("a", 1)))) { reader.next(); Assert.assertEquals(reader.getUUID("u"), uuid); @@ -410,6 +411,9 @@ public void testUuidAndListAccessors() throws Exception { List list = reader.getList("arr"); Assert.assertEquals(list, Arrays.asList(1, 2, 3)); Assert.assertEquals(reader.getList(2), Arrays.asList(1, 2, 3)); + + Assert.assertEquals(reader.getTuple("tuple"), new Object[] {"a", 1}); + Assert.assertEquals(reader.getTuple(3), new Object[] {"a", 1}); } } diff --git a/docs/client-v2-json-support.md b/docs/client-v2-json-support.md index cc72f19e8..233c0ff59 100644 --- a/docs/client-v2-json-support.md +++ b/docs/client-v2-json-support.md @@ -311,6 +311,13 @@ Behavior: the `List` implementation produced by the selected JSON library. Because `JSONEachRow` has no array element metadata, `ResultSet.getArray(...)` is not supported for these inferred JSON arrays. +- Temporal typed JDBC accessors follow the current `JSONEachRowFormatReader` + text-accessor support. `ResultSet.getString(...)` can be used to read the + server-formatted temporal text, but `getTimestamp(...)`, + `getObject(..., Timestamp.class)`, and related temporal conversions are not + guaranteed for `FORMAT JSONEachRow` result sets. Use the binary default + format when JDBC temporal typed accessors are required, or read the value as + a string/object and convert it in application code. - The JSON processor is selected at the connection level through the `jdbc_json_parser_factory` driver property. It cannot be changed per statement, in line with the lifecycle of other connection options. diff --git a/docs/features.md b/docs/features.md index cbb723620..6bbd49c77 100644 --- a/docs/features.md +++ b/docs/features.md @@ -80,7 +80,7 @@ Compatibility-sensitive traits: - String-like ClickHouse values have stable JDBC expectations: `String`, `FixedString`, and `Enum` values are returned as strings, while `UUID` is available both as `getString()` and `getObject(..., UUID.class)`. - `Geometry` has a stable JDBC mapping: metadata reports SQL type `ARRAY` with type name `Geometry`, read paths return nested Java arrays rather than custom wrappers, and write paths depend on the caller preserving the intended point/array nesting shape. - JDBC `Geometry` writes share the same ambiguity as the client serializer: variant selection is inferred from nesting depth, so `Ring` versus `LineString` and `Polygon` versus `MultiLineString` are not currently distinguishable when writing through the generic `Geometry` path. -- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. +- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. JDBC temporal typed accessors such as `getTimestamp(...)` are not guaranteed for JSONEachRow result sets; callers that need stable JDBC temporal conversions should use the binary default format or perform application-level conversion from string/object values. - Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression. - Stream and reader setters (`setAsciiStream`, `setUnicodeStream`, `setBinaryStream`, `setCharacterStream`, `setNCharacterStream`) are treated as text input encoded with the same string-escaping rules, including length-based truncation when a length is supplied. - `getString()` formatting for temporal values is stable output: `Date` uses `yyyy-MM-dd`, `DateTime` uses `yyyy-MM-dd HH:mm:ss`, and `DateTime64` preserves fractional precision, all interpreted in server timezone context where applicable. From aba5ac3f0a20e03d23f4b6733d8c329cc52ccb3c Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 27 May 2026 21:51:27 -0700 Subject: [PATCH 24/26] Made tests for JSONEachRow readers to generate more data and cover more types --- .../clickhouse/data/ClickHouseDataType.java | 3 +- .../AbstractJSONEachRowFormatReaderTests.java | 911 ++++++++++++++---- 2 files changed, 715 insertions(+), 199 deletions(-) diff --git a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java index 5a9ec06c7..f14c2a44e 100644 --- a/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java +++ b/clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java @@ -177,8 +177,7 @@ public static Map, Integer> buildVariantMapping(List>> DATA_TYPE_TO_CLASS = - Collections.unmodifiableMap(dataTypeClassMap()); + public static final Map>> DATA_TYPE_TO_CLASS = dataTypeClassMap(); static Map>> dataTypeClassMap() { Map>> map = new HashMap<>(); diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index c2076c211..d5d0c2bdb 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -11,27 +11,97 @@ import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.ClickHouseFormat; import org.testng.Assert; +import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.Locale; +import java.util.Random; import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; public abstract class AbstractJSONEachRowFormatReaderTests extends BaseIntegrationTest { + /** Number of rows generated per primitive column. */ + private static final int ROW_COUNT = 5; + + /** Fixed seed so generated random values are stable across runs and parser implementations. */ + private static final long RANDOM_SEED = 0xC0FFEEL; + + /** Shared list of primitive cases, populated once per JVM and reused by every test class. */ + private static final List PRIMITIVE_CASES = + buildPrimitiveCases(new Random(RANDOM_SEED)); + protected Client client; + private String primitivesTable; + + @BeforeClass(groups = {"integration"}) + public void setUpPrimitivesTable() throws Exception { + primitivesTable = "test_json_each_row_primitives_" + + getClass().getSimpleName().toLowerCase(Locale.ROOT); + + ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); + try (Client setupClient = new Client.Builder() + .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), isCloud()) + .setUsername("default") + .setPassword(ClickHouseServerForTest.getPassword()) + .build()) { + + setupClient.execute("DROP TABLE IF EXISTS " + primitivesTable).get().close(); + + StringBuilder create = new StringBuilder("CREATE TABLE ") + .append(primitivesTable) + .append(" (id UInt32"); + for (PrimitiveTypeCase c : PRIMITIVE_CASES) { + create.append(", ").append(c.columnName).append(' ').append(c.chType); + } + create.append(") ENGINE = MergeTree ORDER BY id"); + setupClient.execute(create.toString()).get().close(); + + StringBuilder insert = new StringBuilder("INSERT INTO ") + .append(primitivesTable).append(" VALUES "); + for (int row = 0; row < ROW_COUNT; row++) { + if (row > 0) { + insert.append(", "); + } + insert.append('(').append(row); + for (PrimitiveTypeCase c : PRIMITIVE_CASES) { + insert.append(", ").append(c.sqlLiterals.get(row)); + } + insert.append(')'); + } + setupClient.execute(insert.toString()).get().close(); + } + } + + @AfterClass(groups = {"integration"}) + public void tearDownPrimitivesTable() throws Exception { + if (primitivesTable == null) { + return; + } + ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); + try (Client teardownClient = new Client.Builder() + .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), isCloud()) + .setUsername("default") + .setPassword(ClickHouseServerForTest.getPassword()) + .build()) { + teardownClient.execute("DROP TABLE IF EXISTS " + primitivesTable).get().close(); + } + } + @BeforeMethod(groups = {"integration"}) public void setUp() { ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); @@ -54,115 +124,106 @@ private QuerySettings newJsonEachRowSettings() { .setFormat(ClickHouseFormat.JSONEachRow); } + /** + * Settings used by the primitive accessor tests. Integer and floating-point values + * are returned unquoted so they materialise as {@code Number} instances (whose + * {@code longValue}/{@code doubleValue} calls match the typed accessors). + * Decimal values are kept quoted (the ClickHouse default) so that JSON parsers + * that materialise unquoted JSON numbers as {@code Double} (e.g. Jackson with + * default settings) do not lose precision on large {@code Decimal} values. + */ + private QuerySettings newJsonEachRowSettingsForPrimitives() { + return newJsonEachRowSettings() + .serverSetting("output_format_json_quote_64bit_integers", "0") + .serverSetting("output_format_json_quote_64bit_floats", "0") + .serverSetting("output_format_json_quote_decimals", "1"); + } + protected abstract ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException; - @Test(groups = {"integration"}) - public void testBasicParsing() throws Exception { - String table = "test_basic_parsing"; - - List> expected = Arrays.asList( - row(1, "test", true, - Arrays.asList("a", "b", "c"), - mapOf("x", 10, "y", 20)), - row(2, "clickhouse", false, - Collections.singletonList("d"), - mapOf("z", 30))); - - client.execute("DROP TABLE IF EXISTS " + table).get(); - client.execute("CREATE TABLE " + table + " (" + - "id UInt32, " + - "name String, " + - "active Bool, " + - "tags Array(String), " + - "scores Map(String, Int32)" + - ") ENGINE = MergeTree ORDER BY id").get(); - try { - client.execute("INSERT INTO " + table + " VALUES " + buildValuesClause(expected)).get(); - - String sql = "SELECT id, name, active, tags, scores FROM " + table + " ORDER BY id"; - - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { - - for (Map exp : expected) { - Assert.assertTrue(reader.hasNext()); - Assert.assertNotNull(reader.next()); - - Assert.assertEquals(reader.getInteger("id"), ((Number) exp.get("id")).intValue()); - Assert.assertEquals(reader.getString("name"), exp.get("name")); - Assert.assertEquals(reader.getBoolean("active"), exp.get("active")); - Assert.assertEquals(reader.getList("tags"), exp.get("tags")); - - Map actualScores = reader.readValue("scores"); - @SuppressWarnings("unchecked") - Map expectedScores = (Map) exp.get("scores"); - Assert.assertNotNull(actualScores); - Assert.assertEquals(actualScores.size(), expectedScores.size()); - for (Map.Entry e : expectedScores.entrySet()) { - Assert.assertEquals(((Number) actualScores.get(e.getKey())).intValue(), - e.getValue().intValue()); - } - } + // ------------------------------------------------------------------ + // Parameterized primitive value tests + // ------------------------------------------------------------------ - Assert.assertNull(reader.next()); - } - } finally { - client.execute("DROP TABLE IF EXISTS " + table).get(); + @DataProvider(name = "primitiveTypeCases") + public Object[][] primitiveTypeCases() { + Object[][] rows = new Object[PRIMITIVE_CASES.size()][1]; + for (int i = 0; i < PRIMITIVE_CASES.size(); i++) { + rows[i][0] = PRIMITIVE_CASES.get(i); } + return rows; } - private static Map row(int id, String name, boolean active, - List tags, Map scores) { - Map r = new java.util.LinkedHashMap<>(); - r.put("id", id); - r.put("name", name); - r.put("active", active); - r.put("tags", tags); - r.put("scores", scores); - return r; - } + /** + * Runs the same assertions for every primitive type by reading the column populated + * in {@link #setUpPrimitivesTable()}. For each row it asserts that the value can be + * read via the type-appropriate accessor (both by name and by index) and that a set + * of accessors that cannot convert the value throw an exception. + */ + @Test(groups = {"integration"}, dataProvider = "primitiveTypeCases") + public void testPrimitiveTypeAccessors(PrimitiveTypeCase tc) throws Exception { + String sql = "SELECT " + tc.columnName + " FROM " + primitivesTable + " ORDER BY id"; + + try (QueryResponse response = + client.query(sql, newJsonEachRowSettingsForPrimitives()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { - private static Map mapOf(Object... pairs) { - Map m = new java.util.LinkedHashMap<>(); - for (int i = 0; i < pairs.length; i += 2) { - m.put((String) pairs[i], (Integer) pairs[i + 1]); - } - return m; - } + for (int row = 0; row < ROW_COUNT; row++) { + Assert.assertTrue(reader.hasNext(), + "missing row " + row + " for " + tc); + Assert.assertNotNull(reader.next(), + "null row " + row + " for " + tc); - private static String buildValuesClause(List> rows) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < rows.size(); i++) { - if (i > 0) { - sb.append(", "); - } - Map r = rows.get(i); - sb.append('(') - .append(r.get("id")).append(", ") - .append('\'').append(r.get("name")).append("', ") - .append(r.get("active")).append(", "); - - @SuppressWarnings("unchecked") - List tags = (List) r.get("tags"); - sb.append('['); - for (int t = 0; t < tags.size(); t++) { - if (t > 0) sb.append(", "); - sb.append('\'').append(tags.get(t)).append('\''); + Object expected = tc.expectedValues.get(row); + + Object actualByName = tc.readByName.apply(reader, tc.columnName); + Assert.assertTrue(tc.equality.test(actualByName, expected), + "row " + row + " by name for " + tc + + ": expected=" + expected + ", actual=" + actualByName); + + Object actualByIndex = tc.readByIndex.apply(reader, 1); + Assert.assertTrue(tc.equality.test(actualByIndex, expected), + "row " + row + " by index for " + tc + + ": expected=" + expected + ", actual=" + actualByIndex); } - sb.append("], {"); - - @SuppressWarnings("unchecked") - Map scores = (Map) r.get("scores"); - int s = 0; - for (Map.Entry e : scores.entrySet()) { - if (s++ > 0) sb.append(", "); - sb.append('\'').append(e.getKey()).append("': ").append(e.getValue()); + + Assert.assertFalse(reader.hasNext(), "extra rows for " + tc); + Assert.assertNull(reader.next(), "extra row payload for " + tc); + } + + try (QueryResponse response = + client.query(sql, newJsonEachRowSettingsForPrimitives()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { + + Assert.assertNotNull(reader.next(), "row needed for incompatibility checks: " + tc); + + for (IncompatibleAccessor accessor : tc.incompatibleAccessors) { + assertAccessorThrows(reader, accessor.byName, + accessor.name + " by name on " + tc); + assertAccessorThrows(reader, accessor.byIndex, + accessor.name + " by index on " + tc); } - sb.append("})"); } - return sb.toString(); } + private static void assertAccessorThrows(ClickHouseTextFormatReader reader, + Consumer call, + String context) { + try { + call.accept(reader); + Assert.fail("Expected exception when invoking " + context); + } catch (RuntimeException expected) { + // Any RuntimeException is acceptable - DateTimeParseException, + // NumberFormatException, ClassCastException, IllegalArgumentException + // and UnsupportedOperationException are all valid signals that the + // accessor cannot convert the stored value. + } + } + + // ------------------------------------------------------------------ + // Remaining non-value-focused tests + // ------------------------------------------------------------------ + @Test(groups = {"integration"}) public void testSchemaInference() throws Exception { // Numeric inference depends on parser materialization, so this test checks @@ -183,27 +244,6 @@ public void testSchemaInference() throws Exception { } } - @Test(groups = {"integration"}) - public void testDataTypes() throws Exception { - String sql = "SELECT toInt8(120) as b, toInt16(30000) as s, toInt32(1000000) as i, " + - "toInt64(10000000000) as l, toFloat32(1.23) as f, toFloat64(1.23456789) as d, " + - "true as bool, 'hello' as str"; - - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { - - reader.next(); - Assert.assertEquals(reader.getByte("b"), (byte) 120); - Assert.assertEquals(reader.getShort("s"), (short) 30000); - Assert.assertEquals(reader.getInteger("i"), 1000000); - Assert.assertEquals(reader.getLong("l"), 10000000000L); - Assert.assertEquals(reader.getFloat("f"), 1.23f, 0.001f); - Assert.assertEquals(reader.getDouble("d"), 1.23456789d, 0.00000001d); - Assert.assertEquals(reader.getBoolean("bool"), true); - Assert.assertEquals(reader.getString("str"), "hello"); - } - } - @Test(groups = {"integration"}) public void testEmptyData() throws Exception { String sql = "SELECT * FROM remote('127.0.0.1', system.one) WHERE dummy > 1"; @@ -217,29 +257,6 @@ public void testEmptyData() throws Exception { } } - @Test(groups = {"integration"}) - public void testIndexedAccessors() throws Exception { - String sql = "SELECT toInt8(120) as b, toInt16(30000) as s, toInt32(1000000) as i, " + - "toInt64(10000000000) as l, toFloat32(1.5) as f, toFloat64(2.5) as d, " + - "true as bool, 'hello' as str"; - - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { - - reader.next(); - Assert.assertEquals(reader.getByte(1), (byte) 120); - Assert.assertEquals(reader.getShort(2), (short) 30000); - Assert.assertEquals(reader.getInteger(3), 1000000); - Assert.assertEquals(reader.getLong(4), 10000000000L); - Assert.assertEquals(reader.getFloat(5), 1.5f, 0.0001f); - Assert.assertEquals(reader.getDouble(6), 2.5d, 0.0001d); - Assert.assertEquals(reader.getBoolean(7), true); - Assert.assertEquals(reader.getString(8), "hello"); - Assert.assertEquals(reader.getEnum8(1), (byte) 120); - Assert.assertEquals(reader.getEnum16(2), (short) 30000); - } - } - @Test(groups = {"integration"}) public void testReadValueAndHasValue() throws Exception { String sql = "SELECT 7 as id, 'abc' as name, CAST(NULL AS Nullable(String)) as missing"; @@ -263,62 +280,13 @@ public void testReadValueAndHasValue() throws Exception { } @Test(groups = {"integration"}) - public void testBigNumberAccessors() throws Exception { - String sql = "SELECT toInt64(123456789012345) as bi, toDecimal64(12345.6789, 4) as bd"; - - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { - - reader.next(); - Assert.assertEquals(reader.getBigInteger("bi"), BigInteger.valueOf(123456789012345L)); - Assert.assertEquals(reader.getBigInteger(1), BigInteger.valueOf(123456789012345L)); - Assert.assertEquals(reader.getBigDecimal("bd").compareTo(new BigDecimal("12345.6789")), 0); - Assert.assertEquals(reader.getBigDecimal(2).compareTo(new BigDecimal("12345.6789")), 0); - } - } - - @Test(groups = {"integration"}) - public void testTemporalAccessors() throws Exception { - // toDate produces an ISO date string that LocalDate.parse accepts. The - // reader's getLocalDateTime / getLocalTime / getOffsetDateTime delegate - // to the JDK's default ISO parsers, so the remaining columns are - // emitted as strings already shaped to those formats. - String sql = "SELECT toDate('2024-05-06') as d, " + - "'2024-05-06T07:08:09' as dt, " + - "'09:10:11' as t, " + - "'2024-05-06T07:08:09+02:00' as odt"; - - try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); - ClickHouseTextFormatReader reader = createReader(response)) { - - reader.next(); - Assert.assertEquals(reader.getLocalDate("d"), LocalDate.of(2024, 5, 6)); - Assert.assertEquals(reader.getLocalDate(1), LocalDate.of(2024, 5, 6)); - Assert.assertEquals(reader.getLocalDateTime("dt"), - LocalDateTime.of(2024, 5, 6, 7, 8, 9)); - Assert.assertEquals(reader.getLocalDateTime(2), - LocalDateTime.of(2024, 5, 6, 7, 8, 9)); - Assert.assertEquals(reader.getLocalTime("t"), LocalTime.of(9, 10, 11)); - Assert.assertEquals(reader.getLocalTime(3), LocalTime.of(9, 10, 11)); - Assert.assertEquals(reader.getOffsetDateTime("odt"), - OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); - Assert.assertEquals(reader.getOffsetDateTime(4), - OffsetDateTime.parse("2024-05-06T07:08:09+02:00")); - } - } - - @Test(groups = {"integration"}) - public void testUuidAndListAccessors() throws Exception { - String sql = "SELECT toUUID('11111111-2222-3333-4444-555555555555') as u, " + - "[1, 2, 3] as arr"; + public void testListAccessor() throws Exception { + String sql = "SELECT [1, 2, 3] as arr"; try (QueryResponse response = client.query(sql, newJsonEachRowSettings()).get(); ClickHouseTextFormatReader reader = createReader(response)) { reader.next(); - UUID expected = UUID.fromString("11111111-2222-3333-4444-555555555555"); - Assert.assertEquals(reader.getUUID("u"), expected); - Assert.assertEquals(reader.getUUID(1), expected); List values = reader.getList("arr"); Assert.assertNotNull(values); @@ -327,7 +295,7 @@ public void testUuidAndListAccessors() throws Exception { Assert.assertEquals(values.get(1).intValue(), 2); Assert.assertEquals(values.get(2).intValue(), 3); - List byIndex = reader.getList(2); + List byIndex = reader.getList(1); Assert.assertNotNull(byIndex); Assert.assertEquals(byIndex.size(), 3); } @@ -341,4 +309,553 @@ public void testNewBinaryFormatReaderRejectsJsonEachRow() throws Exception { client.newBinaryFormatReader(response); } } + + // ------------------------------------------------------------------ + // Test case definitions for primitive types + // ------------------------------------------------------------------ + + /** + * Describes one primitive ClickHouse column under test. Each case carries the SQL + * literals used to populate the row, the expected Java values, the accessor + * functions that are expected to succeed, and a list of accessors that must throw + * for values of this type. + */ + public static final class PrimitiveTypeCase { + final String columnName; + final String chType; + final List sqlLiterals; + final List expectedValues; + final BiFunction readByName; + final BiFunction readByIndex; + final BiPredicate equality; + final List incompatibleAccessors; + + PrimitiveTypeCase(String columnName, String chType, + List sqlLiterals, + List expectedValues, + BiFunction readByName, + BiFunction readByIndex, + BiPredicate equality, + List incompatibleAccessors) { + this.columnName = columnName; + this.chType = chType; + this.sqlLiterals = sqlLiterals; + this.expectedValues = expectedValues; + this.readByName = readByName; + this.readByIndex = readByIndex; + this.equality = equality; + this.incompatibleAccessors = incompatibleAccessors; + } + + @Override + public String toString() { + return columnName + " (" + chType + ")"; + } + } + + /** Pair of accessor invocations (by name and by index) that must throw for the case's column. */ + public static final class IncompatibleAccessor { + final String name; + final Consumer byName; + final Consumer byIndex; + + IncompatibleAccessor(String name, + Consumer byName, + Consumer byIndex) { + this.name = name; + this.byName = byName; + this.byIndex = byIndex; + } + } + + private static List buildPrimitiveCases(Random rnd) { + List cases = new ArrayList<>(); + + // ---- Signed integers --------------------------------------------------- + cases.add(intCase("col_int8", "Int8", + Arrays.asList( + Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0, + (byte) (rnd.nextInt(256) - 128), + (byte) (rnd.nextInt(256) - 128)), + Number::byteValue, + (r, n) -> r.getByte(n), + (r, i) -> r.getByte(i))); + + cases.add(intCase("col_int16", "Int16", + Arrays.asList( + Short.MIN_VALUE, Short.MAX_VALUE, (short) 0, + (short) (rnd.nextInt(65536) - 32768), + (short) (rnd.nextInt(65536) - 32768)), + Number::shortValue, + (r, n) -> r.getShort(n), + (r, i) -> r.getShort(i))); + + cases.add(intCase("col_int32", "Int32", + Arrays.asList( + Integer.MIN_VALUE, Integer.MAX_VALUE, 0, + rnd.nextInt(), rnd.nextInt()), + Number::intValue, + (r, n) -> r.getInteger(n), + (r, i) -> r.getInteger(i))); + + cases.add(intCase("col_int64", "Int64", + Arrays.asList( + Long.MIN_VALUE, Long.MAX_VALUE, 0L, + rnd.nextLong(), rnd.nextLong()), + Number::longValue, + (r, n) -> r.getLong(n), + (r, i) -> r.getLong(i))); + + // ---- Unsigned integers ------------------------------------------------- + cases.add(intCase("col_uint8", "UInt8", + Arrays.asList( + (short) 0, (short) 255, (short) 128, + (short) rnd.nextInt(256), + (short) rnd.nextInt(256)), + Number::shortValue, + (r, n) -> r.getShort(n), + (r, i) -> r.getShort(i))); + + cases.add(intCase("col_uint16", "UInt16", + Arrays.asList( + 0, 65535, 32768, + rnd.nextInt(65536), + rnd.nextInt(65536)), + Number::intValue, + (r, n) -> r.getInteger(n), + (r, i) -> r.getInteger(i))); + + cases.add(intCase("col_uint32", "UInt32", + Arrays.asList( + 0L, 4294967295L, 1L, + (long) rnd.nextInt() & 0xFFFFFFFFL, + (long) rnd.nextInt() & 0xFFFFFFFFL), + Number::longValue, + (r, n) -> r.getLong(n), + (r, i) -> r.getLong(i))); + + // UInt64 max (2^64 - 1) does not fit into a signed long, so we read it as + // BigInteger and use BigInteger equality everywhere. + List uint64Values = Arrays.asList( + BigInteger.ZERO, + new BigInteger("18446744073709551615"), + BigInteger.ONE, + new BigInteger(63, rnd), + new BigInteger(64, rnd)); + cases.add(new PrimitiveTypeCase( + "col_uint64", "UInt64", + sqlLiteralsFromValues(uint64Values, v -> "toUInt64('" + v + "')"), + uint64Values, + (r, n) -> r.getBigInteger(n), + (r, i) -> r.getBigInteger(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForNumericValue("col_uint64", 1))); + + // ---- Floating-point ---------------------------------------------------- + List floatValues = Arrays.asList( + 0.0f, Float.MAX_VALUE, -Float.MAX_VALUE, + rnd.nextFloat() * 1_000f - 500f, + rnd.nextFloat() * 1_000f - 500f); + cases.add(new PrimitiveTypeCase( + "col_float32", "Float32", + sqlLiteralsFromValues(floatValues, + v -> "toFloat32(" + Float.toString(v) + ")"), + toObjectList(floatValues), + (r, n) -> r.getFloat(n), + (r, i) -> r.getFloat(i), + AbstractJSONEachRowFormatReaderTests::approximatelyEqualsFloat, + incompatibleForNumericValue("col_float32", 1))); + + List doubleValues = Arrays.asList( + 0.0d, Double.MAX_VALUE, -Double.MAX_VALUE, + rnd.nextDouble() * 1_000d - 500d, + rnd.nextDouble() * 1_000d - 500d); + cases.add(new PrimitiveTypeCase( + "col_float64", "Float64", + sqlLiteralsFromValues(doubleValues, + v -> "toFloat64(" + Double.toString(v) + ")"), + toObjectList(doubleValues), + (r, n) -> r.getDouble(n), + (r, i) -> r.getDouble(i), + AbstractJSONEachRowFormatReaderTests::approximatelyEqualsDouble, + incompatibleForNumericValue("col_float64", 1))); + + // ---- Decimal ----------------------------------------------------------- + List decimalValues = Arrays.asList( + new BigDecimal("0.0000"), + new BigDecimal("99999999999999.9999"), + new BigDecimal("-99999999999999.9999"), + new BigDecimal(rnd.nextLong() % 1_000_000_000L) + .movePointLeft(4), + new BigDecimal(rnd.nextLong() % 1_000_000_000L) + .movePointLeft(4)); + cases.add(new PrimitiveTypeCase( + "col_decimal", "Decimal(18, 4)", + sqlLiteralsFromValues(decimalValues, + v -> "toDecimal64('" + v.toPlainString() + "', 4)"), + toObjectList(decimalValues), + (r, n) -> r.getBigDecimal(n), + (r, i) -> r.getBigDecimal(i), + AbstractJSONEachRowFormatReaderTests::equalsBigDecimal, + incompatibleForNumericValue("col_decimal", 1))); + + // ---- Bool -------------------------------------------------------------- + List boolValues = Arrays.asList(false, true, false, rnd.nextBoolean(), rnd.nextBoolean()); + cases.add(new PrimitiveTypeCase( + "col_bool", "Bool", + sqlLiteralsFromValues(boolValues, Object::toString), + toObjectList(boolValues), + (r, n) -> r.getBoolean(n), + (r, i) -> r.getBoolean(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForBoolValue("col_bool", 1))); + + // ---- String ------------------------------------------------------------ + List stringValues = Arrays.asList( + "", + "hello world", + randomAsciiString(rnd, 32), + randomAsciiString(rnd, 16), + "line1\nline2\twith special chars: 'quoted'"); + cases.add(new PrimitiveTypeCase( + "col_string", "String", + sqlLiteralsFromValues(stringValues, + AbstractJSONEachRowFormatReaderTests::toClickHouseStringLiteral), + toObjectList(stringValues), + (r, n) -> r.getString(n), + (r, i) -> r.getString(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForStringValue("col_string", 1))); + + // ---- Date -------------------------------------------------------------- + List dateValues = Arrays.asList( + LocalDate.of(1970, 1, 1), // Date min + LocalDate.of(2149, 6, 6), // Date max + LocalDate.of(2000, 1, 1), + randomDate(rnd), + randomDate(rnd)); + cases.add(new PrimitiveTypeCase( + "col_date", "Date", + sqlLiteralsFromValues(dateValues, v -> "toDate('" + v + "')"), + toObjectList(dateValues), + (r, n) -> r.getLocalDate(n), + (r, i) -> r.getLocalDate(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForDateValue("col_date", 1))); + + // ---- UUID -------------------------------------------------------------- + List uuidValues = Arrays.asList( + new UUID(0L, 0L), + new UUID(-1L, -1L), + UUID.fromString("11111111-2222-3333-4444-555555555555"), + new UUID(rnd.nextLong(), rnd.nextLong()), + new UUID(rnd.nextLong(), rnd.nextLong())); + cases.add(new PrimitiveTypeCase( + "col_uuid", "UUID", + sqlLiteralsFromValues(uuidValues, v -> "toUUID('" + v + "')"), + toObjectList(uuidValues), + (r, n) -> r.getUUID(n), + (r, i) -> r.getUUID(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForUuidValue("col_uuid", 1))); + + return cases; + } + + // ------------------------------------------------------------------ + // Case factories and helpers + // ------------------------------------------------------------------ + + /** + * Builds a case for a signed/unsigned integer column whose expected Java type is a + * boxed integer. Values are passed via {@code toXxx('literal')} casts so the SQL + * parser does not need to evaluate large unary expressions. + */ + private static PrimitiveTypeCase intCase( + String columnName, String chType, + List values, + java.util.function.Function normalize, + BiFunction readByName, + BiFunction readByIndex) { + + List expected = new ArrayList<>(values.size()); + for (Number v : values) { + expected.add(normalize.apply(v)); + } + List literals = sqlLiteralsFromValues(values, + v -> "to" + chType + "('" + v + "')"); + return new PrimitiveTypeCase(columnName, chType, literals, expected, + readByName, readByIndex, + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForNumericValue(columnName, 1)); + } + + private static List sqlLiteralsFromValues(List values, + java.util.function.Function toLiteral) { + List literals = new ArrayList<>(values.size()); + for (T v : values) { + literals.add(toLiteral.apply(v)); + } + return literals; + } + + private static List toObjectList(List values) { + return new ArrayList<>(values); + } + + // ---- Equality helpers ------------------------------------------------------ + + private static boolean equalsByEquals(Object actual, Object expected) { + if (expected == null) { + return actual == null; + } + return expected.equals(actual); + } + + private static boolean equalsBigDecimal(Object actual, Object expected) { + if (expected == null) { + return actual == null; + } + if (!(actual instanceof BigDecimal)) { + return false; + } + return ((BigDecimal) expected).compareTo((BigDecimal) actual) == 0; + } + + private static boolean approximatelyEqualsFloat(Object actual, Object expected) { + if (actual == null || expected == null) { + return actual == expected; + } + float a = ((Number) actual).floatValue(); + float e = ((Number) expected).floatValue(); + if (Float.compare(a, e) == 0) { + return true; + } + if (Float.isInfinite(a) || Float.isInfinite(e) || Float.isNaN(a) || Float.isNaN(e)) { + return Float.compare(a, e) == 0; + } + float tolerance = Math.max(Math.ulp(e) * 4f, Math.abs(e) * 1e-6f); + return Math.abs(a - e) <= tolerance; + } + + private static boolean approximatelyEqualsDouble(Object actual, Object expected) { + if (actual == null || expected == null) { + return actual == expected; + } + double a = ((Number) actual).doubleValue(); + double e = ((Number) expected).doubleValue(); + if (Double.compare(a, e) == 0) { + return true; + } + if (Double.isInfinite(a) || Double.isInfinite(e) || Double.isNaN(a) || Double.isNaN(e)) { + return Double.compare(a, e) == 0; + } + double tolerance = Math.max(Math.ulp(e) * 4d, Math.abs(e) * 1e-12d); + return Math.abs(a - e) <= tolerance; + } + + // ---- Incompatible accessor sets ------------------------------------------- + + /** + * Accessors that always fail (or fail for non-numeric content). For numeric columns + * we rely on date/UUID/temporal accessors to throw because the value cannot be + * parsed as a date or UUID. For unsupported accessors we expect + * {@link UnsupportedOperationException} from the reader implementation. + */ + private static List incompatibleForNumericValue(String name, int index) { + return Arrays.asList( + new IncompatibleAccessor("getLocalDate", + r -> r.getLocalDate(name), + r -> r.getLocalDate(index)), + new IncompatibleAccessor("getLocalTime", + r -> r.getLocalTime(name), + r -> r.getLocalTime(index)), + new IncompatibleAccessor("getLocalDateTime", + r -> r.getLocalDateTime(name), + r -> r.getLocalDateTime(index)), + new IncompatibleAccessor("getUUID", + r -> r.getUUID(name), + r -> r.getUUID(index)), + new IncompatibleAccessor("getZonedDateTime", + r -> r.getZonedDateTime(name), + r -> r.getZonedDateTime(index)), + new IncompatibleAccessor("getInstant", + r -> r.getInstant(name), + r -> r.getInstant(index)), + new IncompatibleAccessor("getInet4Address", + r -> r.getInet4Address(name), + r -> r.getInet4Address(index))); + } + + private static List incompatibleForBoolValue(String name, int index) { + // Numeric accessors fail because Boolean is not a Number, and string parsers + // fail because "true" / "false" cannot be parsed as a number, date, or UUID. + return Arrays.asList( + new IncompatibleAccessor("getByte", + r -> r.getByte(name), + r -> r.getByte(index)), + new IncompatibleAccessor("getInteger", + r -> r.getInteger(name), + r -> r.getInteger(index)), + new IncompatibleAccessor("getLong", + r -> r.getLong(name), + r -> r.getLong(index)), + new IncompatibleAccessor("getBigInteger", + r -> r.getBigInteger(name), + r -> r.getBigInteger(index)), + new IncompatibleAccessor("getBigDecimal", + r -> r.getBigDecimal(name), + r -> r.getBigDecimal(index)), + new IncompatibleAccessor("getLocalDate", + r -> r.getLocalDate(name), + r -> r.getLocalDate(index)), + new IncompatibleAccessor("getUUID", + r -> r.getUUID(name), + r -> r.getUUID(index)), + new IncompatibleAccessor("getZonedDateTime", + r -> r.getZonedDateTime(name), + r -> r.getZonedDateTime(index))); + } + + private static List incompatibleForStringValue(String name, int index) { + // String content here is not numeric, not a date, and not a UUID, so numeric + // and temporal accessors must throw. + return Arrays.asList( + new IncompatibleAccessor("getByte", + r -> r.getByte(name), + r -> r.getByte(index)), + new IncompatibleAccessor("getInteger", + r -> r.getInteger(name), + r -> r.getInteger(index)), + new IncompatibleAccessor("getLong", + r -> r.getLong(name), + r -> r.getLong(index)), + new IncompatibleAccessor("getDouble", + r -> r.getDouble(name), + r -> r.getDouble(index)), + new IncompatibleAccessor("getBigInteger", + r -> r.getBigInteger(name), + r -> r.getBigInteger(index)), + new IncompatibleAccessor("getBigDecimal", + r -> r.getBigDecimal(name), + r -> r.getBigDecimal(index)), + new IncompatibleAccessor("getLocalDate", + r -> r.getLocalDate(name), + r -> r.getLocalDate(index)), + new IncompatibleAccessor("getUUID", + r -> r.getUUID(name), + r -> r.getUUID(index)), + new IncompatibleAccessor("getZonedDateTime", + r -> r.getZonedDateTime(name), + r -> r.getZonedDateTime(index))); + } + + private static List incompatibleForDateValue(String name, int index) { + // Date columns arrive as strings, so numeric accessors throw ClassCastException + // and time-only / date-time accessors fail to parse the YYYY-MM-DD shape. + return Arrays.asList( + new IncompatibleAccessor("getInteger", + r -> r.getInteger(name), + r -> r.getInteger(index)), + new IncompatibleAccessor("getLong", + r -> r.getLong(name), + r -> r.getLong(index)), + new IncompatibleAccessor("getDouble", + r -> r.getDouble(name), + r -> r.getDouble(index)), + new IncompatibleAccessor("getBigDecimal", + r -> r.getBigDecimal(name), + r -> r.getBigDecimal(index)), + new IncompatibleAccessor("getLocalTime", + r -> r.getLocalTime(name), + r -> r.getLocalTime(index)), + new IncompatibleAccessor("getLocalDateTime", + r -> r.getLocalDateTime(name), + r -> r.getLocalDateTime(index)), + new IncompatibleAccessor("getUUID", + r -> r.getUUID(name), + r -> r.getUUID(index)), + new IncompatibleAccessor("getZonedDateTime", + r -> r.getZonedDateTime(name), + r -> r.getZonedDateTime(index))); + } + + private static List incompatibleForUuidValue(String name, int index) { + return Arrays.asList( + new IncompatibleAccessor("getInteger", + r -> r.getInteger(name), + r -> r.getInteger(index)), + new IncompatibleAccessor("getLong", + r -> r.getLong(name), + r -> r.getLong(index)), + new IncompatibleAccessor("getDouble", + r -> r.getDouble(name), + r -> r.getDouble(index)), + new IncompatibleAccessor("getBigDecimal", + r -> r.getBigDecimal(name), + r -> r.getBigDecimal(index)), + new IncompatibleAccessor("getLocalDate", + r -> r.getLocalDate(name), + r -> r.getLocalDate(index)), + new IncompatibleAccessor("getLocalDateTime", + r -> r.getLocalDateTime(name), + r -> r.getLocalDateTime(index)), + new IncompatibleAccessor("getZonedDateTime", + r -> r.getZonedDateTime(name), + r -> r.getZonedDateTime(index))); + } + + // ---- Random value helpers ------------------------------------------------- + + private static String randomAsciiString(Random rnd, int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + // Printable ASCII range except quote (32-126 except 39 and 92). + char c; + do { + c = (char) (32 + rnd.nextInt(95)); + } while (c == '\'' || c == '\\'); + sb.append(c); + } + return sb.toString(); + } + + private static LocalDate randomDate(Random rnd) { + // Stay well inside the Date range (1970-01-01..2149-06-06) to avoid time-zone + // edge effects when the server re-serialises the value into JSON. + int year = 1971 + rnd.nextInt(170); + int month = 1 + rnd.nextInt(12); + int day = 1 + rnd.nextInt(LocalDate.of(year, month, 1).lengthOfMonth()); + return LocalDate.of(year, month, day); + } + + private static String toClickHouseStringLiteral(String value) { + StringBuilder sb = new StringBuilder(value.length() + 2); + sb.append('\''); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '\'': + sb.append("\\'"); + break; + case '\\': + sb.append("\\\\"); + break; + case '\n': + sb.append("\\n"); + break; + case '\t': + sb.append("\\t"); + break; + case '\r': + sb.append("\\r"); + break; + default: + sb.append(c); + break; + } + } + sb.append('\''); + return sb.toString(); + } } From 7cbe271bdcbb7f0db3e3110ca392c5a102b240a6 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 27 May 2026 22:19:01 -0700 Subject: [PATCH 25/26] Covered arrays --- .../data_formats/JSONEachRowFormatReader.java | 139 +++++++++- .../AbstractJSONEachRowFormatReaderTests.java | 256 +++++++++++++++++- .../GsonJSONEachRowFormatReaderTests.java | 6 + .../JSONEachRowFormatReaderTest.java | 157 +++++++++-- .../JacksonJSONEachRowFormatReaderTests.java | 6 + 5 files changed, 530 insertions(+), 34 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index b01e6f3fc..45e110ae0 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -1,5 +1,7 @@ package com.clickhouse.client.api.data_formats; +import com.clickhouse.client.api.ClientException; +import com.clickhouse.client.api.data_formats.internal.NumberConverter; import com.clickhouse.client.api.internal.SchemaUtils; import com.clickhouse.client.api.metadata.TableSchema; import com.clickhouse.data.ClickHouseColumn; @@ -9,6 +11,7 @@ import com.clickhouse.data.value.ClickHouseGeoPolygonValue; import com.clickhouse.data.value.ClickHouseGeoRingValue; +import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.net.Inet4Address; @@ -151,9 +154,21 @@ public double getDouble(String colName) { @Override public boolean getBoolean(String colName) { Object val = currentRow.get(colName); - if (val instanceof Boolean) return (Boolean) val; - if (val instanceof Number) return ((Number) val).intValue() != 0; - return Boolean.parseBoolean(val.toString()); + if (val instanceof Boolean) { + return (Boolean) val; + } + if (val instanceof Number) { + // Match AbstractBinaryFormatReader (SerializerUtils.convertToBoolean): + // any non-zero integral value is true, zero is false. Fractional + // values keep the same behavior because they are truncated by + // longValue() before the zero check. + return ((Number) val).longValue() != 0; + } + if (val == null) { + throw new ClientException("Column '" + colName + "' has null value and cannot be converted to boolean"); + } + throw new ClientException("Cannot convert value of type " + val.getClass().getName() + + " in column '" + colName + "' to boolean"); } @Override @@ -224,52 +239,150 @@ public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { @Override public List getList(String colName) { - return (List) currentRow.get(colName); + Object val = currentRow.get(colName); + if (val == null) { + return null; + } + if (!(val instanceof List)) { + throw new ClientException("Column '" + colName + "' is not of array type (actual: " + + val.getClass().getName() + ")"); + } + return (List) val; } @Override public byte[] getByteArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, byte.class); } @Override public int[] getIntArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, int.class); } @Override public long[] getLongArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, long.class); } @Override public float[] getFloatArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, float.class); } @Override public double[] getDoubleArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, double.class); } @Override public boolean[] getBooleanArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, boolean.class); } @Override public short[] getShortArray(String colName) { - throw new UnsupportedOperationException(); + return getPrimitiveArray(colName, short.class); } @Override public String[] getStringArray(String colName) { - throw new UnsupportedOperationException(); + List list = asArrayList(colName); + if (list == null) { + return null; + } + String[] out = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + Object el = list.get(i); + out[i] = el == null ? null : el.toString(); + } + return out; } @Override public Object[] getObjectArray(String colName) { - throw new UnsupportedOperationException(); + List list = asArrayList(colName); + return list == null ? null : list.toArray(new Object[0]); + } + + /** + * Returns the value of the given column as a {@code List}, or {@code null} + * if the value is missing. Throws {@link ClientException} when the column + * exists but is not an array. + */ + private List asArrayList(String colName) { + Object val = currentRow.get(colName); + if (val == null) { + return null; + } + if (!(val instanceof List)) { + throw new ClientException("Column '" + colName + "' is not of array type (actual: " + + val.getClass().getName() + ")"); + } + return (List) val; + } + + @SuppressWarnings("unchecked") + private T getPrimitiveArray(String colName, Class componentType) { + List list = asArrayList(colName); + if (list == null) { + return null; + } + try { + Object array = Array.newInstance(componentType, list.size()); + for (int i = 0; i < list.size(); i++) { + Object el = list.get(i); + if (el == null) { + throw new ClientException("Column '" + colName + + "' contains a null element which cannot fit into an array of primitive " + + componentType.getName()); + } + Array.set(array, i, coerceToComponent(el, componentType)); + } + return (T) array; + } catch (ClassCastException | IllegalArgumentException e) { + throw new ClientException("Value of column '" + colName + + "' cannot be converted to an array of " + componentType.getName(), e); + } + } + + /** + * Coerces a parsed JSON element to a boxed primitive type. JSON parsers + * may materialize numeric array elements as different boxed types + * (e.g. {@code Integer}, {@code Long}, {@code Double}, {@code BigDecimal}), + * so element-level conversion is necessary before populating a typed + * primitive array. + */ + private static Object coerceToComponent(Object value, Class componentType) { + if (componentType == byte.class) { + return NumberConverter.toByte(value); + } + if (componentType == short.class) { + return NumberConverter.toShort(value); + } + if (componentType == int.class) { + return NumberConverter.toInt(value); + } + if (componentType == long.class) { + return NumberConverter.toLong(value); + } + if (componentType == float.class) { + return NumberConverter.toFloat(value); + } + if (componentType == double.class) { + return NumberConverter.toDouble(value); + } + if (componentType == boolean.class) { + if (value instanceof Boolean) { + return value; + } + if (value instanceof Number) { + return ((Number) value).longValue() != 0; + } + throw new IllegalArgumentException( + "Cannot convert " + value.getClass().getName() + " to boolean array element"); + } + return value; } @Override diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index d5d0c2bdb..97f6d6085 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -18,9 +18,12 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -141,6 +144,13 @@ private QuerySettings newJsonEachRowSettingsForPrimitives() { protected abstract ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException; + /** + * Builds a reader directly over the provided JSONEachRow byte stream, bypassing + * the server. This is used to exercise error paths (such as corrupted input) + * deterministically across both parser factories. + */ + protected abstract ClickHouseTextFormatReader createReader(InputStream input) throws IOException; + // ------------------------------------------------------------------ // Parameterized primitive value tests // ------------------------------------------------------------------ @@ -310,6 +320,168 @@ public void testNewBinaryFormatReaderRejectsJsonEachRow() throws Exception { } } + // ------------------------------------------------------------------ + // Corrupted-stream coverage for the readNextRow error path + // ------------------------------------------------------------------ + + /** + * Builds a reader directly over a hand-crafted JSONEachRow stream that + * contains a valid first object followed by malformed bytes. The first + * call to {@code next()} buffered row #1 in the constructor and then + * tries to read row #2, which is malformed; the reader must surface a + * {@link RuntimeException} and refuse to advance any further. + * + *

This is structured as a unit-style test so it does not depend on the + * server, but it stays in the {@code integration} group alongside the + * other reader tests because it is parser-factory specific.

+ */ + @Test(groups = {"integration"}) + public void testCorruptedStreamFailsReadNextRow() throws Exception { + // The third "row" deliberately contains an unterminated string after + // a partial object so that lenient parsers cannot quietly accept it. + // The newline separation matches real JSONEachRow framing. + String body = "{\"id\":1,\"name\":\"first\"}\n" + + "{\"id\":2,\"name\":\"second\"}\n" + + "{\"id\":3,\"name\":\"unterminated"; + + try (InputStream input = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + ClickHouseTextFormatReader reader = createReader(input)) { + + Assert.assertTrue(reader.hasNext(), "first row should be buffered"); + + Assert.assertNotNull(reader.next(), "row 1 should be readable"); + Assert.assertEquals(reader.getString("name"), "first"); + + try { + reader.next(); + Assert.fail("Expected RuntimeException reading malformed row"); + } catch (RuntimeException expected) { + // any RuntimeException is acceptable - both Jackson and Gson + // surface different concrete exception types here, but the + // reader wraps them into a RuntimeException. + } + + Assert.assertFalse(reader.hasNext(), + "reader must report end-of-stream after a parse failure"); + Assert.assertNull(reader.next(), + "next() must return null after a parse failure, not retry the stream"); + } + } + + // ------------------------------------------------------------------ + // Boolean accessor: numeric -> boolean coverage + // ------------------------------------------------------------------ + + /** + * Verifies that {@code getBoolean} converts numeric ClickHouse columns to + * boolean using the "non-zero is true" rule, mirroring + * {@code AbstractBinaryFormatReader} semantics. The test pushes literal + * 0/1 values through several integer widths and also exercises the + * native Bool column. + */ + @Test(groups = {"integration"}) + public void testGetBooleanFromNumericValues() throws Exception { + String sql = "SELECT " + + "toInt8(0) AS v_int8_zero, toInt8(1) AS v_int8_one, " + + "toInt32(0) AS v_int32_zero, toInt32(42) AS v_int32_nonzero, " + + "toInt64(0) AS v_int64_zero, toInt64(-7) AS v_int64_nonzero, " + + "toUInt64(1) AS v_uint64_one, " + + "true AS v_bool_true, false AS v_bool_false"; + + try (QueryResponse response = + client.query(sql, newJsonEachRowSettingsForPrimitives()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { + + Assert.assertNotNull(reader.next()); + + Assert.assertFalse(reader.getBoolean("v_int8_zero")); + Assert.assertTrue(reader.getBoolean("v_int8_one")); + Assert.assertFalse(reader.getBoolean("v_int32_zero")); + Assert.assertTrue(reader.getBoolean("v_int32_nonzero")); + Assert.assertFalse(reader.getBoolean("v_int64_zero")); + Assert.assertTrue(reader.getBoolean("v_int64_nonzero")); + Assert.assertTrue(reader.getBoolean("v_uint64_one")); + Assert.assertTrue(reader.getBoolean("v_bool_true")); + Assert.assertFalse(reader.getBoolean("v_bool_false")); + + // The same values must also be readable by 1-based column index. + Assert.assertFalse(reader.getBoolean(1)); + Assert.assertTrue(reader.getBoolean(2)); + } + } + + // ------------------------------------------------------------------ + // Array accessor coverage + // ------------------------------------------------------------------ + + /** + * Exercises the typed Array accessors against a row of mixed Array(...) + * columns. Verifies that: + * + *
    + *
  • {@code getList} returns the parser-native list.
  • + *
  • The typed primitive accessors coerce parsed JSON numbers into the + * requested primitive type, regardless of whether the parser + * materialized elements as {@code Integer}, {@code Long}, + * {@code Double}, or {@code BigDecimal}.
  • + *
  • The typed accessors throw on non-array columns.
  • + *
+ */ + @Test(groups = {"integration"}) + public void testArrayAccessors() throws Exception { + String sql = "SELECT " + + "[1, 2, 3]::Array(Int32) AS col_int_arr, " + + "[10, 20, 30]::Array(Int64) AS col_long_arr, " + + "[1, 2]::Array(Int16) AS col_short_arr, " + + "[7, 8]::Array(Int8) AS col_byte_arr, " + + "[1.5, 2.5]::Array(Float64) AS col_double_arr, " + + "[1.0, 2.0]::Array(Float32) AS col_float_arr, " + + "['a', 'b', 'c']::Array(String) AS col_string_arr, " + + "[true, false, true]::Array(Bool) AS col_bool_arr, " + + "toInt32(1) AS col_not_array"; + + try (QueryResponse response = + client.query(sql, newJsonEachRowSettingsForPrimitives()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { + + Assert.assertNotNull(reader.next()); + + List intList = reader.getList("col_int_arr"); + Assert.assertNotNull(intList); + Assert.assertEquals(intList.size(), 3); + + Assert.assertEquals(reader.getIntArray("col_int_arr"), new int[] {1, 2, 3}); + Assert.assertEquals(reader.getIntArray(1), new int[] {1, 2, 3}); + Assert.assertEquals(reader.getLongArray("col_long_arr"), new long[] {10L, 20L, 30L}); + Assert.assertEquals(reader.getLongArray(2), new long[] {10L, 20L, 30L}); + Assert.assertEquals(reader.getShortArray("col_short_arr"), new short[] {(short) 1, (short) 2}); + Assert.assertEquals(reader.getByteArray("col_byte_arr"), new byte[] {(byte) 7, (byte) 8}); + Assert.assertEquals(reader.getDoubleArray("col_double_arr"), new double[] {1.5d, 2.5d}, 1e-9); + Assert.assertEquals(reader.getFloatArray("col_float_arr"), new float[] {1.0f, 2.0f}, 1e-6f); + Assert.assertEquals(reader.getStringArray("col_string_arr"), new String[] {"a", "b", "c"}); + Assert.assertEquals(reader.getBooleanArray("col_bool_arr"), + new boolean[] {true, false, true}); + + Object[] objs = reader.getObjectArray("col_int_arr"); + Assert.assertEquals(objs.length, 3); + + // Non-array columns must surface a RuntimeException rather than + // silently returning null or a malformed array. + try { + reader.getIntArray("col_not_array"); + Assert.fail("Expected exception on scalar column"); + } catch (RuntimeException expected) { + // ok + } + try { + reader.getList("col_not_array"); + Assert.fail("Expected exception on scalar column"); + } catch (RuntimeException expected) { + // ok + } + } + } + // ------------------------------------------------------------------ // Test case definitions for primitive types // ------------------------------------------------------------------ @@ -559,6 +731,37 @@ private static List buildPrimitiveCases(Random rnd) { AbstractJSONEachRowFormatReaderTests::equalsByEquals, incompatibleForUuidValue("col_uuid", 1))); + // ---- Enum8 / Enum16 ---------------------------------------------------- + // ClickHouse serialises enum columns into JSONEachRow as their string + // labels (not as the underlying numeric value). The reader therefore + // exposes them through getString, while numeric and temporal accessors + // remain incompatible. The 1-based column index passed to the + // incompatible-accessor helper is always 1 because each parameterized + // run selects a single column. + List enum8Values = Arrays.asList( + "red", "green", "blue", "red", "green"); + cases.add(new PrimitiveTypeCase( + "col_enum8", "Enum8('red' = 1, 'green' = 2, 'blue' = 3)", + sqlLiteralsFromValues(enum8Values, + AbstractJSONEachRowFormatReaderTests::toClickHouseStringLiteral), + toObjectList(enum8Values), + (r, n) -> r.getString(n), + (r, i) -> r.getString(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForEnumValue("col_enum8", 1))); + + List enum16Values = Arrays.asList( + "alpha", "beta", "gamma", "alpha", "beta"); + cases.add(new PrimitiveTypeCase( + "col_enum16", "Enum16('alpha' = 100, 'beta' = 200, 'gamma' = 300)", + sqlLiteralsFromValues(enum16Values, + AbstractJSONEachRowFormatReaderTests::toClickHouseStringLiteral), + toObjectList(enum16Values), + (r, n) -> r.getString(n), + (r, i) -> r.getString(i), + AbstractJSONEachRowFormatReaderTests::equalsByEquals, + incompatibleForEnumValue("col_enum16", 1))); + return cases; } @@ -719,7 +922,10 @@ private static List incompatibleForBoolValue(String name, private static List incompatibleForStringValue(String name, int index) { // String content here is not numeric, not a date, and not a UUID, so numeric - // and temporal accessors must throw. + // and temporal accessors must throw. getBoolean is also expected to throw + // because the JSONEachRow reader does not silently convert string content + // through Boolean.parseBoolean (matching AbstractBinaryFormatReader-style + // strictness about incompatible types). return Arrays.asList( new IncompatibleAccessor("getByte", r -> r.getByte(name), @@ -733,6 +939,9 @@ private static List incompatibleForStringValue(String name new IncompatibleAccessor("getDouble", r -> r.getDouble(name), r -> r.getDouble(index)), + new IncompatibleAccessor("getBoolean", + r -> r.getBoolean(name), + r -> r.getBoolean(index)), new IncompatibleAccessor("getBigInteger", r -> r.getBigInteger(name), r -> r.getBigInteger(index)), @@ -780,6 +989,51 @@ private static List incompatibleForDateValue(String name, r -> r.getZonedDateTime(index))); } + private static List incompatibleForEnumValue(String name, int index) { + // ClickHouse serialises Enum8/Enum16 columns as their string label in + // JSONEachRow output, so numeric and temporal accessors must throw and + // getBoolean must also throw (the reader does not silently parse + // string content into a boolean). getEnum8/getEnum16 forward to + // getByte/getShort, so they must throw on a string value as well. + return Arrays.asList( + new IncompatibleAccessor("getByte", + r -> r.getByte(name), + r -> r.getByte(index)), + new IncompatibleAccessor("getShort", + r -> r.getShort(name), + r -> r.getShort(index)), + new IncompatibleAccessor("getInteger", + r -> r.getInteger(name), + r -> r.getInteger(index)), + new IncompatibleAccessor("getLong", + r -> r.getLong(name), + r -> r.getLong(index)), + new IncompatibleAccessor("getDouble", + r -> r.getDouble(name), + r -> r.getDouble(index)), + new IncompatibleAccessor("getBoolean", + r -> r.getBoolean(name), + r -> r.getBoolean(index)), + new IncompatibleAccessor("getBigInteger", + r -> r.getBigInteger(name), + r -> r.getBigInteger(index)), + new IncompatibleAccessor("getBigDecimal", + r -> r.getBigDecimal(name), + r -> r.getBigDecimal(index)), + new IncompatibleAccessor("getEnum8", + r -> r.getEnum8(name), + r -> r.getEnum8(index)), + new IncompatibleAccessor("getEnum16", + r -> r.getEnum16(name), + r -> r.getEnum16(index)), + new IncompatibleAccessor("getLocalDate", + r -> r.getLocalDate(name), + r -> r.getLocalDate(index)), + new IncompatibleAccessor("getUUID", + r -> r.getUUID(name), + r -> r.getUUID(index))); + } + private static List incompatibleForUuidValue(String name, int index) { return Arrays.asList( new IncompatibleAccessor("getInteger", diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java index bcebe96da..57f4e1abe 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/GsonJSONEachRowFormatReaderTests.java @@ -12,6 +12,7 @@ import org.testng.annotations.Test; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStreamWriter; import java.util.Collections; import java.util.Map; @@ -28,6 +29,11 @@ protected ClickHouseTextFormatReader createReader(QueryResponse response) throws return new JSONEachRowFormatReader(parserFactory.createJsonParser(response.getInputStream())); } + @Override + protected ClickHouseTextFormatReader createReader(InputStream input) throws IOException { + return new JSONEachRowFormatReader(parserFactory.createJsonParser(input)); + } + @Test(groups = {"integration"}) public void testRowToObject() throws Exception { diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index 7cbb22651..e478f19a1 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -340,16 +340,59 @@ public void testBooleanAccessor() throws Exception { "from_bool", Boolean.TRUE, "from_zero", 0, "from_nonzero", 1, - "from_string", "true"))) { + "from_long", 42L, + "from_big_int", new BigInteger("9000000000"), + "from_big_dec_zero", BigDecimal.ZERO))) { reader.next(); Assert.assertTrue(reader.getBoolean("from_bool")); Assert.assertFalse(reader.getBoolean("from_zero")); Assert.assertTrue(reader.getBoolean("from_nonzero")); - Assert.assertTrue(reader.getBoolean("from_string")); + Assert.assertTrue(reader.getBoolean("from_long")); + Assert.assertTrue(reader.getBoolean("from_big_int")); + Assert.assertFalse(reader.getBoolean("from_big_dec_zero")); Assert.assertTrue(reader.getBoolean(1)); } } + @Test + public void testBooleanAccessorRejectsStringValue() throws Exception { + // Aligning with AbstractBinaryFormatReader expectations: text values + // that are not numeric or boolean are rejected rather than silently + // funneled through Boolean.parseBoolean, which would silently treat + // any non-"true" string as false. + try (JSONEachRowFormatReader reader = readerOf(row("v", "true"))) { + reader.next(); + try { + reader.getBoolean("v"); + Assert.fail("Expected exception for string value"); + } catch (RuntimeException expected) { + // ok + } + try { + reader.getBoolean(1); + Assert.fail("Expected exception for string value"); + } catch (RuntimeException expected) { + // ok + } + } + } + + @Test + public void testBooleanAccessorRejectsNullValue() throws Exception { + Map r = new LinkedHashMap<>(); + r.put("v", null); + try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader( + new StubJsonParser(Collections.singletonList(r)))) { + reader.next(); + try { + reader.getBoolean("v"); + Assert.fail("Expected exception for null value"); + } catch (RuntimeException expected) { + // ok + } + } + } + @Test public void testBigNumberAccessors() throws Exception { try (JSONEachRowFormatReader reader = readerOf(row( @@ -444,24 +487,6 @@ public void testUnsupportedAccessorsThrow() throws Exception { assertUnsupported(() -> reader.getGeoPolygon(1)); assertUnsupported(() -> reader.getGeoMultiPolygon("v")); assertUnsupported(() -> reader.getGeoMultiPolygon(1)); - assertUnsupported(() -> reader.getByteArray("v")); - assertUnsupported(() -> reader.getByteArray(1)); - assertUnsupported(() -> reader.getIntArray("v")); - assertUnsupported(() -> reader.getIntArray(1)); - assertUnsupported(() -> reader.getLongArray("v")); - assertUnsupported(() -> reader.getLongArray(1)); - assertUnsupported(() -> reader.getFloatArray("v")); - assertUnsupported(() -> reader.getFloatArray(1)); - assertUnsupported(() -> reader.getDoubleArray("v")); - assertUnsupported(() -> reader.getDoubleArray(1)); - assertUnsupported(() -> reader.getBooleanArray("v")); - assertUnsupported(() -> reader.getBooleanArray(1)); - assertUnsupported(() -> reader.getShortArray("v")); - assertUnsupported(() -> reader.getShortArray(1)); - assertUnsupported(() -> reader.getStringArray("v")); - assertUnsupported(() -> reader.getStringArray(1)); - assertUnsupported(() -> reader.getObjectArray("v")); - assertUnsupported(() -> reader.getObjectArray(1)); assertUnsupported(() -> reader.getClickHouseBitmap("v")); assertUnsupported(() -> reader.getClickHouseBitmap(1)); assertUnsupported(() -> reader.getTemporalAmount("v")); @@ -469,6 +494,98 @@ public void testUnsupportedAccessorsThrow() throws Exception { } } + @Test + public void testArrayAccessorsReturnNullForNullValue() throws Exception { + Map r = new LinkedHashMap<>(); + r.put("v", null); + try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader( + new StubJsonParser(Collections.singletonList(r)))) { + reader.next(); + Assert.assertNull(reader.getList("v")); + Assert.assertNull(reader.getIntArray("v")); + Assert.assertNull(reader.getLongArray("v")); + Assert.assertNull(reader.getDoubleArray("v")); + Assert.assertNull(reader.getBooleanArray("v")); + Assert.assertNull(reader.getStringArray("v")); + Assert.assertNull(reader.getObjectArray("v")); + } + } + + @Test + public void testArrayAccessorsRejectNonArrayValues() throws Exception { + try (JSONEachRowFormatReader reader = readerOf(row("v", "x"))) { + reader.next(); + assertThrowsRuntime(() -> reader.getList("v")); + assertThrowsRuntime(() -> reader.getList(1)); + assertThrowsRuntime(() -> reader.getIntArray("v")); + assertThrowsRuntime(() -> reader.getIntArray(1)); + assertThrowsRuntime(() -> reader.getLongArray("v")); + assertThrowsRuntime(() -> reader.getLongArray(1)); + assertThrowsRuntime(() -> reader.getShortArray("v")); + assertThrowsRuntime(() -> reader.getShortArray(1)); + assertThrowsRuntime(() -> reader.getByteArray("v")); + assertThrowsRuntime(() -> reader.getByteArray(1)); + assertThrowsRuntime(() -> reader.getFloatArray("v")); + assertThrowsRuntime(() -> reader.getFloatArray(1)); + assertThrowsRuntime(() -> reader.getDoubleArray("v")); + assertThrowsRuntime(() -> reader.getDoubleArray(1)); + assertThrowsRuntime(() -> reader.getBooleanArray("v")); + assertThrowsRuntime(() -> reader.getBooleanArray(1)); + assertThrowsRuntime(() -> reader.getStringArray("v")); + assertThrowsRuntime(() -> reader.getStringArray(1)); + assertThrowsRuntime(() -> reader.getObjectArray("v")); + assertThrowsRuntime(() -> reader.getObjectArray(1)); + } + } + + @Test + public void testArrayAccessorsCoercePrimitiveElements() throws Exception { + // Different parsers materialize numbers as different boxed types + // (Integer, Long, Double, BigDecimal). The reader must coerce each + // element to the requested primitive type without losing data. + try (JSONEachRowFormatReader reader = readerOf(row( + "ints", Arrays.asList(1, 2, 3), + "longs", Arrays.asList(10L, 20L, 30L), + "doubles", Arrays.asList(1.5d, 2.5d), + "floats", Arrays.asList(1.0f, 2.0f), + "shorts", Arrays.asList((short) 10, (short) 20), + "bytes", Arrays.asList((byte) 1, (byte) 2), + "bools", Arrays.asList(Boolean.TRUE, Boolean.FALSE, Boolean.TRUE), + "strs", Arrays.asList("a", "b"), + "big_decs", Arrays.asList(new BigDecimal("4"), new BigDecimal("5"))))) { + reader.next(); + + Assert.assertEquals(reader.getIntArray("ints"), new int[] {1, 2, 3}); + Assert.assertEquals(reader.getLongArray("longs"), new long[] {10L, 20L, 30L}); + Assert.assertEquals(reader.getDoubleArray("doubles"), new double[] {1.5d, 2.5d}, 1e-9); + Assert.assertEquals(reader.getFloatArray("floats"), new float[] {1.0f, 2.0f}, 1e-6f); + Assert.assertEquals(reader.getShortArray("shorts"), new short[] {(short) 10, (short) 20}); + Assert.assertEquals(reader.getByteArray("bytes"), new byte[] {(byte) 1, (byte) 2}); + Assert.assertEquals(reader.getBooleanArray("bools"), new boolean[] {true, false, true}); + Assert.assertEquals(reader.getStringArray("strs"), new String[] {"a", "b"}); + + // BigDecimal -> int[] requires element-level coercion. + Assert.assertEquals(reader.getIntArray("big_decs"), new int[] {4, 5}); + + // getObjectArray preserves boxed element types. + Object[] objs = reader.getObjectArray("ints"); + Assert.assertEquals(objs.length, 3); + + // getList returns the parser-native list reference. + List list = reader.getList("ints"); + Assert.assertEquals(list, Arrays.asList(1, 2, 3)); + } + } + + private static void assertThrowsRuntime(Runnable r) { + try { + r.run(); + Assert.fail("Expected RuntimeException"); + } catch (RuntimeException expected) { + // ok + } + } + private static void assertUnsupported(Runnable r) { try { r.run(); diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java index 85d41ff54..03aaab38b 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JacksonJSONEachRowFormatReaderTests.java @@ -4,6 +4,7 @@ import org.testng.annotations.Test; import java.io.IOException; +import java.io.InputStream; @Test(groups = {"integration"}) public class JacksonJSONEachRowFormatReaderTests extends AbstractJSONEachRowFormatReaderTests { @@ -14,4 +15,9 @@ public class JacksonJSONEachRowFormatReaderTests extends AbstractJSONEachRowForm protected ClickHouseTextFormatReader createReader(QueryResponse response) throws IOException { return new JSONEachRowFormatReader(parserFactory.createJsonParser(response.getInputStream())); } + + @Override + protected ClickHouseTextFormatReader createReader(InputStream input) throws IOException { + return new JSONEachRowFormatReader(parserFactory.createJsonParser(input)); + } } From ed814b1f2200cffd755773fc484874ca4da0c54f Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 27 May 2026 22:27:11 -0700 Subject: [PATCH 26/26] covered getters by index --- .../data_formats/JSONEachRowFormatReader.java | 6 +- .../AbstractJSONEachRowFormatReaderTests.java | 74 ++++++++++- .../JSONEachRowFormatReaderTest.java | 120 ++++++++++++++++++ 3 files changed, 192 insertions(+), 8 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java index 45e110ae0..0f9fa444a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java @@ -351,7 +351,9 @@ private T getPrimitiveArray(String colName, Class componentType) { * may materialize numeric array elements as different boxed types * (e.g. {@code Integer}, {@code Long}, {@code Double}, {@code BigDecimal}), * so element-level conversion is necessary before populating a typed - * primitive array. + * primitive array. The {@code componentType} is always one of the eight + * Java primitives passed by {@link #getPrimitiveArray}; unsupported + * component types are rejected explicitly to keep the helper total. */ private static Object coerceToComponent(Object value, Class componentType) { if (componentType == byte.class) { @@ -382,7 +384,7 @@ private static Object coerceToComponent(Object value, Class componentType) { throw new IllegalArgumentException( "Cannot convert " + value.getClass().getName() + " to boolean array element"); } - return value; + throw new IllegalArgumentException("Unsupported component type: " + componentType.getName()); } @Override diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java index 97f6d6085..a747f5bf1 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java @@ -450,20 +450,33 @@ public void testArrayAccessors() throws Exception { Assert.assertNotNull(intList); Assert.assertEquals(intList.size(), 3); + // Each typed accessor is exercised by name first and then by 1-based + // column index so the index-based overloads are also covered against + // the inferred schema produced from the first row. Assert.assertEquals(reader.getIntArray("col_int_arr"), new int[] {1, 2, 3}); Assert.assertEquals(reader.getIntArray(1), new int[] {1, 2, 3}); Assert.assertEquals(reader.getLongArray("col_long_arr"), new long[] {10L, 20L, 30L}); Assert.assertEquals(reader.getLongArray(2), new long[] {10L, 20L, 30L}); - Assert.assertEquals(reader.getShortArray("col_short_arr"), new short[] {(short) 1, (short) 2}); + Assert.assertEquals(reader.getShortArray("col_short_arr"), + new short[] {(short) 1, (short) 2}); + Assert.assertEquals(reader.getShortArray(3), new short[] {(short) 1, (short) 2}); Assert.assertEquals(reader.getByteArray("col_byte_arr"), new byte[] {(byte) 7, (byte) 8}); - Assert.assertEquals(reader.getDoubleArray("col_double_arr"), new double[] {1.5d, 2.5d}, 1e-9); - Assert.assertEquals(reader.getFloatArray("col_float_arr"), new float[] {1.0f, 2.0f}, 1e-6f); - Assert.assertEquals(reader.getStringArray("col_string_arr"), new String[] {"a", "b", "c"}); + Assert.assertEquals(reader.getByteArray(4), new byte[] {(byte) 7, (byte) 8}); + Assert.assertEquals(reader.getDoubleArray("col_double_arr"), + new double[] {1.5d, 2.5d}, 1e-9); + Assert.assertEquals(reader.getDoubleArray(5), new double[] {1.5d, 2.5d}, 1e-9); + Assert.assertEquals(reader.getFloatArray("col_float_arr"), + new float[] {1.0f, 2.0f}, 1e-6f); + Assert.assertEquals(reader.getFloatArray(6), new float[] {1.0f, 2.0f}, 1e-6f); + Assert.assertEquals(reader.getStringArray("col_string_arr"), + new String[] {"a", "b", "c"}); + Assert.assertEquals(reader.getStringArray(7), new String[] {"a", "b", "c"}); Assert.assertEquals(reader.getBooleanArray("col_bool_arr"), new boolean[] {true, false, true}); + Assert.assertEquals(reader.getBooleanArray(8), new boolean[] {true, false, true}); - Object[] objs = reader.getObjectArray("col_int_arr"); - Assert.assertEquals(objs.length, 3); + Assert.assertEquals(reader.getObjectArray("col_int_arr").length, 3); + Assert.assertEquals(reader.getObjectArray(1).length, 3); // Non-array columns must surface a RuntimeException rather than // silently returning null or a malformed array. @@ -482,6 +495,55 @@ public void testArrayAccessors() throws Exception { } } + /** + * Locks in the contract that a {@code null} element coming from the server + * (e.g. through {@code Array(Nullable(...))}) cannot be silently turned + * into a zero/false slot of a Java primitive array. The reader must throw + * a {@link RuntimeException} so callers don't lose the distinction between + * "missing value" and "actual zero". + */ + @Test(groups = {"integration"}) + public void testArrayAccessorsRejectNullElementsFromServer() throws Exception { + String sql = "SELECT " + + "[1, NULL, 3]::Array(Nullable(Int32)) AS col_int_arr, " + + "[true, NULL]::Array(Nullable(Bool)) AS col_bool_arr, " + + "['x', NULL, 'z']::Array(Nullable(String)) AS col_string_arr"; + + try (QueryResponse response = + client.query(sql, newJsonEachRowSettingsForPrimitives()).get(); + ClickHouseTextFormatReader reader = createReader(response)) { + + Assert.assertNotNull(reader.next()); + + try { + reader.getIntArray("col_int_arr"); + Assert.fail("Expected exception on null element in primitive array"); + } catch (RuntimeException expected) { + // ok + } + try { + reader.getBooleanArray("col_bool_arr"); + Assert.fail("Expected exception on null element in primitive array"); + } catch (RuntimeException expected) { + // ok + } + + // getStringArray and getObjectArray preserve nulls because the + // resulting array can hold null references. + Assert.assertEquals(reader.getStringArray("col_string_arr"), + new String[] {"x", null, "z"}); + Object[] objs = reader.getObjectArray("col_string_arr"); + Assert.assertEquals(objs.length, 3); + Assert.assertNull(objs[1]); + + // getList must surface the raw list with the null element intact. + List intList = reader.getList("col_int_arr"); + Assert.assertNotNull(intList); + Assert.assertEquals(intList.size(), 3); + Assert.assertNull(intList.get(1)); + } + } + // ------------------------------------------------------------------ // Test case definitions for primitive types // ------------------------------------------------------------------ diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java index e478f19a1..f45fe8676 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java @@ -501,13 +501,63 @@ public void testArrayAccessorsReturnNullForNullValue() throws Exception { try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader( new StubJsonParser(Collections.singletonList(r)))) { reader.next(); + + // By name: every array accessor must propagate the null cleanly + // rather than throw NullPointerException or fabricate an empty array. Assert.assertNull(reader.getList("v")); + Assert.assertNull(reader.getByteArray("v")); + Assert.assertNull(reader.getShortArray("v")); Assert.assertNull(reader.getIntArray("v")); Assert.assertNull(reader.getLongArray("v")); + Assert.assertNull(reader.getFloatArray("v")); Assert.assertNull(reader.getDoubleArray("v")); Assert.assertNull(reader.getBooleanArray("v")); Assert.assertNull(reader.getStringArray("v")); Assert.assertNull(reader.getObjectArray("v")); + + // By 1-based index: the same null-propagation guarantee must hold + // for the index-based overloads, which delegate to the name-based + // implementations through the inferred schema. + Assert.assertNull(reader.getList(1)); + Assert.assertNull(reader.getByteArray(1)); + Assert.assertNull(reader.getShortArray(1)); + Assert.assertNull(reader.getIntArray(1)); + Assert.assertNull(reader.getLongArray(1)); + Assert.assertNull(reader.getFloatArray(1)); + Assert.assertNull(reader.getDoubleArray(1)); + Assert.assertNull(reader.getBooleanArray(1)); + Assert.assertNull(reader.getStringArray(1)); + Assert.assertNull(reader.getObjectArray(1)); + } + } + + @Test + public void testArrayAccessorsRejectNullElement() throws Exception { + // A null element in a JSON array cannot be stored into a Java + // primitive array slot, so getPrimitiveArray must surface this rather + // than substitute a zero/false value silently. The String and Object + // overloads are allowed to keep the null element. + try (JSONEachRowFormatReader reader = readerOf(row( + "ints", Arrays.asList(1, null, 3), + "bools", Arrays.asList(Boolean.TRUE, null), + "strs", Arrays.asList("a", null, "c"), + "objs", Arrays.asList("x", null)))) { + reader.next(); + + assertThrowsRuntime(() -> reader.getIntArray("ints")); + assertThrowsRuntime(() -> reader.getIntArray(1)); + assertThrowsRuntime(() -> reader.getLongArray("ints")); + assertThrowsRuntime(() -> reader.getShortArray("ints")); + assertThrowsRuntime(() -> reader.getByteArray("ints")); + assertThrowsRuntime(() -> reader.getFloatArray("ints")); + assertThrowsRuntime(() -> reader.getDoubleArray("ints")); + assertThrowsRuntime(() -> reader.getBooleanArray("bools")); + + // The non-primitive container accessors keep nulls. + Assert.assertEquals(reader.getStringArray("strs"), new String[] {"a", null, "c"}); + Object[] objs = reader.getObjectArray("objs"); + Assert.assertEquals(objs.length, 2); + Assert.assertNull(objs[1]); } } @@ -538,6 +588,76 @@ public void testArrayAccessorsRejectNonArrayValues() throws Exception { } } + @Test + public void testArrayAccessorsByIndex() throws Exception { + // Mirrors testArrayAccessorsCoercePrimitiveElements but addresses every + // typed array accessor through its 1-based column index. This ensures + // the index-based overloads delegate correctly through the inferred + // schema, even for arrays where the schema only records "Array" without + // a specific element type. + try (JSONEachRowFormatReader reader = readerOf(row( + "ints", Arrays.asList(1, 2, 3), + "longs", Arrays.asList(10L, 20L, 30L), + "shorts", Arrays.asList((short) 4, (short) 5), + "bytes", Arrays.asList((byte) 6, (byte) 7), + "doubles", Arrays.asList(1.5d, 2.5d), + "floats", Arrays.asList(1.0f, 2.0f), + "bools", Arrays.asList(Boolean.TRUE, Boolean.FALSE), + "strs", Arrays.asList("a", "b")))) { + reader.next(); + + Assert.assertEquals(reader.getIntArray(1), new int[] {1, 2, 3}); + Assert.assertEquals(reader.getLongArray(2), new long[] {10L, 20L, 30L}); + Assert.assertEquals(reader.getShortArray(3), new short[] {(short) 4, (short) 5}); + Assert.assertEquals(reader.getByteArray(4), new byte[] {(byte) 6, (byte) 7}); + Assert.assertEquals(reader.getDoubleArray(5), new double[] {1.5d, 2.5d}, 1e-9); + Assert.assertEquals(reader.getFloatArray(6), new float[] {1.0f, 2.0f}, 1e-6f); + Assert.assertEquals(reader.getBooleanArray(7), new boolean[] {true, false}); + Assert.assertEquals(reader.getStringArray(8), new String[] {"a", "b"}); + + // getList and getObjectArray by index, on the first column. + List list = reader.getList(1); + Assert.assertEquals(list, Arrays.asList(1, 2, 3)); + Object[] objs = reader.getObjectArray(1); + Assert.assertEquals(objs.length, 3); + } + } + + @Test + public void testGetBooleanArrayFromNumericList() throws Exception { + // Exercises the Number branch in coerceToComponent for boolean[]: the + // reader treats 0 as false and any non-zero value as true, regardless + // of whether the parser materialized the element as Integer, Long, or + // BigDecimal. + try (JSONEachRowFormatReader reader = readerOf(row( + "as_int", Arrays.asList(0, 1, 2, -1), + "as_long", Arrays.asList(0L, 5L), + "as_big_decimal", Arrays.asList(BigDecimal.ZERO, new BigDecimal("3"))))) { + reader.next(); + + Assert.assertEquals(reader.getBooleanArray("as_int"), + new boolean[] {false, true, true, true}); + Assert.assertEquals(reader.getBooleanArray("as_long"), + new boolean[] {false, true}); + Assert.assertEquals(reader.getBooleanArray("as_big_decimal"), + new boolean[] {false, true}); + } + } + + @Test + public void testGetBooleanArrayRejectsNonBooleanNonNumberElements() throws Exception { + // String elements cannot be coerced into boolean[] (we don't accept + // string-to-boolean parsing on the scalar getBoolean accessor either), + // so getBooleanArray must surface a RuntimeException sourced from the + // illegal-coerce branch in coerceToComponent. + try (JSONEachRowFormatReader reader = readerOf(row( + "v", Arrays.asList("true", "false")))) { + reader.next(); + assertThrowsRuntime(() -> reader.getBooleanArray("v")); + assertThrowsRuntime(() -> reader.getBooleanArray(1)); + } + } + @Test public void testArrayAccessorsCoercePrimitiveElements() throws Exception { // Different parsers materialize numbers as different boxed types