diff --git a/clickhouse-client/pom.xml b/clickhouse-client/pom.xml
index a219686dc..df7cf24d5 100644
--- a/clickhouse-client/pom.xml
+++ b/clickhouse-client/pom.xml
@@ -33,6 +33,11 @@
caffeine
provided
+
+ com.google.code.gson
+ gson
+ provided
+
dnsjava
dnsjava
diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java
index 1a44571d3..533f333d1 100644
--- a/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java
+++ b/clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseChecker.java
@@ -173,8 +173,29 @@ public static BigDecimal between(BigDecimal value, String name, BigDecimal minVa
* @param value the string to check
* @return true if the string is null or empty; false otherwise
*/
- public static boolean isNullOrEmpty(String value) {
- return value == null || value.isEmpty();
+ public static boolean isNullOrEmpty(CharSequence value) {
+ return value == null || value.length() == 0;
+ }
+
+ /**
+ * Checks if the given string is null, empty string or a string only contains
+ * white spaces.
+ *
+ * @param value the string to check
+ * @return true if the string is null, empty or blankļ¼ false otherwise
+ */
+ public static boolean isNullOrBlank(CharSequence value) {
+ if (isNullOrEmpty(value)) {
+ return true;
+ }
+
+ for (int i = 0, len = value.length(); i < len; i++) {
+ if (!Character.isWhitespace(value.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/data/JsonStreamUtils.java b/clickhouse-client/src/main/java/com/clickhouse/client/data/JsonStreamUtils.java
new file mode 100644
index 000000000..d3af6c6c6
--- /dev/null
+++ b/clickhouse-client/src/main/java/com/clickhouse/client/data/JsonStreamUtils.java
@@ -0,0 +1,58 @@
+package com.clickhouse.client.data;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import com.google.gson.Gson;
+
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * Utility class for reading and writing objects in JSON format.
+ */
+public final class JsonStreamUtils {
+ private static final Gson gson = new Gson();
+
+ public static T readObject(InputStream input, Class clazz) throws IOException {
+ return readObject(input, StandardCharsets.UTF_8, clazz);
+ }
+
+ public static T readObject(InputStream input, Charset charset, Class clazz) throws IOException {
+ return readObject(new InputStreamReader(input, charset), clazz);
+ }
+
+ public static T readObject(Reader reader, Class clazz) throws IOException {
+ return gson.fromJson(reader, clazz);
+ }
+
+ public static T readObject(String json, Class clazz) throws IOException {
+ return gson.fromJson(json, clazz);
+ }
+
+ public static void writeObject(OutputStream output, T object) throws IOException {
+ writeObject(output, StandardCharsets.UTF_8, object);
+ }
+
+ public static void writeObject(OutputStream output, Charset charset, T object) throws IOException {
+ OutputStreamWriter writer = new OutputStreamWriter(output, charset);
+ writeObject(writer, object);
+ writer.flush();
+ }
+
+ public static void writeObject(Writer writer, T object) throws IOException {
+ gson.toJson(object, object == null ? Object.class : object.getClass(), gson.newJsonWriter(writer));
+ }
+
+ public static String toJsonString(Object object) {
+ return gson.toJson(object);
+ }
+
+ private JsonStreamUtils() {
+ }
+}
diff --git a/clickhouse-client/src/main/java9/module-info.java b/clickhouse-client/src/main/java9/module-info.java
index b6ab399e8..02958f9fc 100644
--- a/clickhouse-client/src/main/java9/module-info.java
+++ b/clickhouse-client/src/main/java9/module-info.java
@@ -14,11 +14,13 @@
com.clickhouse.client.postgresql,
// native is a reserved keyword :<
com.clickhouse.client.tcp,
- com.clickhouse.jdbc;
+ com.clickhouse.jdbc,
+ ru.yandex.clickhouse;
requires java.base;
requires static java.logging;
+ requires static com.google.gson;
requires static com.github.benmanes.caffeine;
requires static org.dnsjava;
requires static org.lz4.java;
diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java
index a431bcbb7..0cc051d33 100644
--- a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseCheckerTest.java
@@ -31,10 +31,21 @@ public void testBetween() {
@Test(groups = { "unit" })
public void testIsNullOrEmpty() {
Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(null));
+ Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(new StringBuilder()));
+ Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(new StringBuffer()));
Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(""));
Assert.assertFalse(ClickHouseChecker.isNullOrEmpty(" "));
}
+ @Test(groups = { "unit" })
+ public void testIsNullOrBlank() {
+ Assert.assertTrue(ClickHouseChecker.isNullOrBlank(null));
+ Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(new StringBuilder()));
+ Assert.assertTrue(ClickHouseChecker.isNullOrEmpty(new StringBuffer()));
+ Assert.assertTrue(ClickHouseChecker.isNullOrBlank(""));
+ Assert.assertTrue(ClickHouseChecker.isNullOrBlank(" \t\r\n "));
+ }
+
@Test(groups = { "unit" })
public void testNonEmpty() {
Assert.assertEquals(ClickHouseChecker.nonEmpty(" ", "value"), " ");
diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/data/JsonStreamUtilsTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/data/JsonStreamUtilsTest.java
new file mode 100644
index 000000000..75a5abf57
--- /dev/null
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/data/JsonStreamUtilsTest.java
@@ -0,0 +1,236 @@
+package com.clickhouse.client.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class JsonStreamUtilsTest {
+ static class JsonCompactResponse {
+ private List meta;
+ private List> data;
+ private List totals;
+ private Extremes extremes;
+ private int rows;
+ private int rows_before_limit_at_least;
+
+ public static class Extremes {
+ private List min;
+ private List max;
+
+ public List getMin() {
+ return min;
+ }
+
+ public void setMin(List min) {
+ this.min = min;
+ }
+
+ public List getMax() {
+ return max;
+ }
+
+ public void setMax(List max) {
+ this.max = max;
+ }
+ }
+
+ public static class Meta {
+ private String name;
+ private String type;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "Meta{" + "name='" + name + '\'' + ", type='" + type + '\'' + '}';
+ }
+ }
+
+ public Extremes getExtremes() {
+ return extremes;
+ }
+
+ public void setExtremes(Extremes extremes) {
+ this.extremes = extremes;
+ }
+
+ public List getMeta() {
+ return meta;
+ }
+
+ public void setMeta(List meta) {
+ this.meta = meta;
+ }
+
+ public List> getData() {
+ return data;
+ }
+
+ public void setData(List> data) {
+ this.data = data;
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ public void setRows(int rows) {
+ this.rows = rows;
+ }
+
+ public int getRows_before_limit_at_least() {
+ return rows_before_limit_at_least;
+ }
+
+ public void setRows_before_limit_at_least(int rows_before_limit_at_least) {
+ this.rows_before_limit_at_least = rows_before_limit_at_least;
+ }
+
+ public List getTotals() {
+ return totals;
+ }
+
+ public void setTotals(List totals) {
+ this.totals = totals;
+ }
+
+ @Override
+ public String toString() {
+ return "ClickHouseResponse{" + "meta=" + meta + ", data=" + data + ", rows=" + rows
+ + ", rows_before_limit_at_least=" + rows_before_limit_at_least + '}';
+ }
+ }
+
+ static class JsonResponseSummary {
+ private final long read_rows; // number of read rows for selects (may be more than rows in result set)
+ private final long written_rows; // number of written rows for inserts
+ private final long read_bytes;
+ private final long written_bytes;
+ private final long total_rows_to_read;
+
+ public JsonResponseSummary(long read_rows, long written_rows, long read_bytes, long written_bytes,
+ long total_rows_to_read) {
+ this.read_rows = read_rows;
+ this.written_rows = written_rows;
+ this.read_bytes = read_bytes;
+ this.written_bytes = written_bytes;
+ this.total_rows_to_read = total_rows_to_read;
+ }
+
+ public long getReadRows() {
+ return read_rows;
+ }
+
+ public long getWrittenRows() {
+ return written_rows;
+ }
+
+ public long getReadBytes() {
+ return read_bytes;
+ }
+
+ public long getWrittenBytes() {
+ return written_bytes;
+ }
+
+ public long getTotalRowsToRead() {
+ return total_rows_to_read;
+ }
+ }
+
+ @Test(groups = { "unit" })
+ public void testReadObject() throws IOException {
+ Assert.assertThrows(NullPointerException.class,
+ () -> JsonStreamUtils.readObject((InputStream) null, JsonCompactResponse.class));
+ Assert.assertThrows(NullPointerException.class,
+ () -> JsonStreamUtils.readObject((Reader) null, JsonCompactResponse.class));
+
+ Assert.assertNull(JsonStreamUtils.readObject((String) null, JsonCompactResponse.class));
+ Assert.assertNull(JsonStreamUtils.readObject("", JsonCompactResponse.class));
+ Assert.assertNull(JsonStreamUtils.readObject(new ByteArrayInputStream(new byte[0]), JsonCompactResponse.class));
+
+ JsonCompactResponse r = JsonStreamUtils.readObject("{}", JsonCompactResponse.class);
+ Assert.assertNotNull(r);
+ Assert.assertNull(r.getMeta());
+ Assert.assertNull(r.getData());
+ Assert.assertNull(r.getTotals());
+ Assert.assertNull(r.getExtremes());
+ Assert.assertEquals(r.getRows(), 0);
+ Assert.assertEquals(r.getRows_before_limit_at_least(), 0);
+
+ // set extremes=1
+ // select 123 as a group by a with totals limit 5 format JSONCompact
+ String json = "{\"meta\":[{\"name\":\"123\",\"type\":\"UInt8\"}],\"data\":[[123]],\"totals\":[123],"
+ + "\"extremes\":{\"min\":[123],\"max\":[123]},\"rows\":1,\"rows_before_limit_at_least\":1,"
+ + "\"statistics\":{\"elapsed\":0.0008974,\"rows_read\":1,\"bytes_read\":1}}";
+
+ r = JsonStreamUtils.readObject(new ByteArrayInputStream(json.getBytes()), JsonCompactResponse.class);
+ Assert.assertNotNull(r);
+ Assert.assertEquals(r.getMeta().size(), 1);
+ Assert.assertEquals(r.getMeta().get(0).toString(), "Meta{name='123\', type='UInt8\'}");
+
+ Assert.assertEquals(r.getData().size(), 1);
+ Assert.assertEquals(r.getData().get(0), Collections.singleton("123"));
+
+ Assert.assertEquals(r.getTotals(), Collections.singleton("123"));
+
+ Assert.assertNotNull(r.getExtremes());
+ Assert.assertEquals(r.getExtremes().getMin(), Collections.singleton("123"));
+ Assert.assertEquals(r.getExtremes().getMax(), Collections.singleton("123"));
+
+ Assert.assertEquals(r.getRows(), 1);
+ Assert.assertEquals(r.getRows_before_limit_at_least(), 1);
+
+ // map based on property name
+ JsonResponseSummary rs = JsonStreamUtils.readObject(
+ "{\"read_rows\":1,\"written_rows\":2,\"read_bytes\":3,\"written_bytes\":4,\"total_rows_to_read\":5}",
+ JsonResponseSummary.class);
+
+ Assert.assertNotNull(rs);
+ Assert.assertEquals(rs.getReadRows(), 1);
+ Assert.assertEquals(rs.getWrittenRows(), 2);
+ Assert.assertEquals(rs.getReadBytes(), 3);
+ Assert.assertEquals(rs.getWrittenBytes(), 4);
+ Assert.assertEquals(rs.getTotalRowsToRead(), 5);
+ }
+
+ @Test(groups = { "unit" })
+ public void testToJsonString() {
+ Assert.assertEquals(JsonStreamUtils.toJsonString(null), "null");
+ Assert.assertEquals(JsonStreamUtils.toJsonString(""), "\"\"");
+ Assert.assertEquals(JsonStreamUtils.toJsonString(1), "1");
+ Assert.assertEquals(JsonStreamUtils.toJsonString(new Object()), "{}");
+ Assert.assertEquals(JsonStreamUtils.toJsonString(new JsonCompactResponse()),
+ "{\"rows\":0,\"rows_before_limit_at_least\":0}");
+ }
+
+ @Test(groups = { "unit" })
+ public void testWriteObject() throws IOException {
+ try (ByteArrayOutputStream output = new ByteArrayOutputStream(1024)) {
+ JsonStreamUtils.writeObject(output, new JsonCompactResponse());
+ output.flush();
+ Assert.assertEquals(new String(output.toByteArray()), "{\"rows\":0,\"rows_before_limit_at_least\":0}");
+ }
+ }
+}
diff --git a/clickhouse-jdbc/pom.xml b/clickhouse-jdbc/pom.xml
index d5fbd97ef..28c20a6c9 100644
--- a/clickhouse-jdbc/pom.xml
+++ b/clickhouse-jdbc/pom.xml
@@ -64,6 +64,22 @@
+
+ ${project.parent.groupId}
+ org.roaringbitmap
+ ${repackaged.version}
+ provided
+
+
+ *
+ *
+
+
+
+
+ com.google.code.gson
+ gson
+
org.apache.httpcomponents
httpclient
@@ -78,19 +94,6 @@
org.lz4
lz4-java
-
- com.fasterxml.jackson.core
- jackson-core
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
- org.roaringbitmap
- RoaringBitmap
- provided
-
org.slf4j
slf4j-api
@@ -204,8 +207,8 @@
shaded
- com.fasterxml.jackson
- ${shade.base}.jackson
+ com.google.gson
+ ${shade.base}.gson
org.apache
diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/BalancedClickhouseDataSource.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/BalancedClickhouseDataSource.java
index 3bf9a4b36..363426534 100644
--- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/BalancedClickhouseDataSource.java
+++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/BalancedClickhouseDataSource.java
@@ -2,9 +2,11 @@
import org.slf4j.LoggerFactory;
import ru.yandex.clickhouse.settings.ClickHouseProperties;
-import ru.yandex.clickhouse.util.apache.StringUtils;
import javax.sql.DataSource;
+
+import com.clickhouse.client.ClickHouseChecker;
+
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.sql.SQLException;
@@ -328,7 +330,7 @@ private static ClickHouseProperties getFromUrl(String url) {
}
private static Properties getFromUrlWithoutDefault(String url) {
- if (StringUtils.isBlank(url))
+ if (ClickHouseChecker.isNullOrBlank(url))
return new Properties();
int index = url.indexOf("?");
diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java
index df28df16f..e107014e4 100644
--- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java
+++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/ClickHouseStatementImpl.java
@@ -19,6 +19,9 @@
import java.util.Objects;
import java.util.TimeZone;
import java.util.UUID;
+
+import com.clickhouse.client.data.JsonStreamUtils;
+
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@@ -294,7 +297,7 @@ protected ClickHouseResponse executeQueryClickhouseResponse(
stmt = applyFormat(stmt, ClickHouseFormat.JSONCompact);
try (InputStream is = getInputStream(stmt, additionalDBParams, null, additionalRequestParams)) {
- return Jackson.getObjectMapper().readValue(
+ return JsonStreamUtils.readObject(
properties.isCompress() ? new ClickHouseLZ4Stream(is) : is, ClickHouseResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -375,7 +378,7 @@ public ClickHouseResponse executeQueryClickhouseResponse(String sql,
parseSqlStatements(sql, ClickHouseFormat.JSONCompact, additionalDBParams);
try (InputStream is = getLastInputStream(additionalDBParams, null, additionalRequestParams)) {
- return Jackson.getObjectMapper().readValue(
+ return JsonStreamUtils.readObject(
properties.isCompress() ? new ClickHouseLZ4Stream(is) : is, ClickHouseResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -795,7 +798,7 @@ private InputStream getInputStream(
// retrieve response summary
if (isQueryParamSet(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, additionalClickHouseDBParams, additionalRequestParams)) {
Header summaryHeader = response.getFirstHeader("X-ClickHouse-Summary");
- currentSummary = summaryHeader != null ? Jackson.getObjectMapper().readValue(summaryHeader.getValue(), ClickHouseResponseSummary.class) : null;
+ currentSummary = summaryHeader != null ? JsonStreamUtils.readObject(summaryHeader.getValue(), ClickHouseResponseSummary.class) : null;
}
return is;
@@ -1074,7 +1077,7 @@ void sendStream(Writer writer, HttpEntity content) throws ClickHouseException {
// retrieve response summary
if (isQueryParamSet(ClickHouseQueryParam.SEND_PROGRESS_IN_HTTP_HEADERS, writer.getAdditionalDBParams(), writer.getRequestParams())) {
Header summaryHeader = response.getFirstHeader("X-ClickHouse-Summary");
- currentSummary = summaryHeader != null ? Jackson.getObjectMapper().readValue(summaryHeader.getValue(), ClickHouseResponseSummary.class) : null;
+ currentSummary = summaryHeader != null ? JsonStreamUtils.readObject(summaryHeader.getValue(), ClickHouseResponseSummary.class) : null;
}
} catch (ClickHouseException e) {
throw e;
diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/Jackson.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/Jackson.java
deleted file mode 100644
index 4c56351f1..000000000
--- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/Jackson.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package ru.yandex.clickhouse;
-
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-public class Jackson {
-
- private static final ThreadLocal om = new ThreadLocal() {
- @Override
- protected ObjectMapper initialValue() {
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- return objectMapper;
- }
- };
-
- /**
- * Flyweight thread local objectMapper. Users of this should not keep a reference to this.
- * @return an ObjectMapper
- */
- public static ObjectMapper getObjectMapper() {
- return om.get();
- }
-}
diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/PreparedStatementParser.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/PreparedStatementParser.java
index 093725463..ea837f125 100644
--- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/PreparedStatementParser.java
+++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/PreparedStatementParser.java
@@ -6,7 +6,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import ru.yandex.clickhouse.util.apache.StringUtils;
+import com.clickhouse.client.ClickHouseChecker;
/**
* Parser for JDBC SQL Strings.
@@ -37,7 +37,7 @@ static PreparedStatementParser parse(String sql) {
@Deprecated
static PreparedStatementParser parse(String sql, int valuesEndPosition) {
- if (StringUtils.isBlank(sql)) {
+ if (ClickHouseChecker.isNullOrBlank(sql)) {
throw new IllegalArgumentException("SQL may not be blank");
}
PreparedStatementParser parser = new PreparedStatementParser();
diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ArrayToStringDeserializer.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ArrayToStringDeserializer.java
deleted file mode 100644
index 1187fbc4c..000000000
--- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ArrayToStringDeserializer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package ru.yandex.clickhouse.response;
-
-import com.clickhouse.client.ClickHouseCache;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.type.TypeFactory;
-import ru.yandex.clickhouse.Jackson;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-class ArrayToStringDeserializer extends JsonDeserializer> {
- private static final ClickHouseCache> deserializers = ClickHouseCache.create(1000, 300,
- (ctx) -> {
- try {
- return ctx.findContextualValueDeserializer(
- TypeFactory.defaultInstance().constructType(new TypeReference>() {
- }), null);
- } catch (JsonMappingException e) {
- throw new IllegalStateException(e);
- }
- });
-
- @Override
- public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
- JsonDeserializer
-
- com.fasterxml.jackson.core
- jackson-core
- ${jackson.version}
-
-
- com.fasterxml.jackson.core
- jackson-databind
- ${jackson.version}
-
com.google.code.gson
gson