Skip to content

Commit 6f75cb3

Browse files
committed
fixed createArray
1 parent f9d0a55 commit 6f75cb3

File tree

5 files changed

+109
-91
lines changed

5 files changed

+109
-91
lines changed

jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.sql.Clob;
2424
import java.sql.Connection;
2525
import java.sql.DatabaseMetaData;
26+
import java.sql.JDBCType;
2627
import java.sql.NClob;
2728
import java.sql.PreparedStatement;
2829
import java.sql.ResultSet;
@@ -564,17 +565,31 @@ public Properties getClientInfo() throws SQLException {
564565

565566
@Override
566567
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
568+
ensureOpen();
569+
if (typeName == null) {
570+
throw new SQLFeatureNotSupportedException("typeName cannot be null");
571+
}
572+
573+
int parentPos = typeName.indexOf('(');
574+
String clickhouseDataTypeName = (typeName.substring(0, parentPos == -1 ? typeName.length() : parentPos)).trim();
575+
ClickHouseDataType dataType = ClickHouseDataType.valueOf(clickhouseDataTypeName);
576+
if (dataType.equals(ClickHouseDataType.Array)) {
577+
throw new SQLFeatureNotSupportedException("Array cannot be a base type. In case of nested array provide most deep element type name.");
578+
}
567579
try {
568-
List<Object> list =
569-
(elements == null || elements.length == 0) ? Collections.emptyList() : Arrays.stream(elements, 0, elements.length).collect(Collectors.toList());
570-
return new com.clickhouse.jdbc.types.Array(list, typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName)).getVendorTypeNumber());
580+
return new com.clickhouse.jdbc.types.Array(clickhouseDataTypeName,
581+
JdbcUtils.CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(dataType, JDBCType.OTHER).getVendorTypeNumber(), elements);
571582
} catch (Exception e) {
572583
throw new SQLException("Failed to create array", ExceptionUtils.SQL_STATE_CLIENT_ERROR, e);
573584
}
574585
}
575586

576587
@Override
577588
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
589+
ensureOpen();
590+
if (typeName == null) {
591+
throw new SQLFeatureNotSupportedException("typeName cannot be null");
592+
}
578593
ClickHouseColumn column = ClickHouseColumn.of("v", typeName);
579594
if (column.getDataType().equals(ClickHouseDataType.Tuple)) {
580595
return new com.clickhouse.jdbc.types.Struct(column, attributes);

jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java

Lines changed: 50 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.sql.SQLType;
4040
import java.sql.SQLXML;
4141
import java.sql.Statement;
42+
import java.sql.Struct;
4243
import java.sql.Time;
4344
import java.sql.Timestamp;
4445
import java.time.Instant;
@@ -749,102 +750,84 @@ public final int executeUpdate(String sql, String[] columnNames) throws SQLExcep
749750
"executeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
750751
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
751752
}
752-
private static String encodeObject(Object x) throws SQLException {
753+
private String encodeObject(Object x) throws SQLException {
753754
return encodeObject(x, null);
754755
}
756+
757+
private static final char QUOTE = '\'';
755758

756-
private static String encodeObject(Object x, Long length) throws SQLException {
759+
private String encodeObject(Object x, Long length) throws SQLException {
757760
LOG.trace("Encoding object: {}", x);
758761

759762
try {
760763
if (x == null) {
761764
return "NULL";
762765
} else if (x instanceof String) {
763-
return "'" + SQLUtils.escapeSingleQuotes((String) x) + "'";
766+
return QUOTE + SQLUtils.escapeSingleQuotes((String) x) + QUOTE;
764767
} else if (x instanceof Boolean) {
765768
return (Boolean) x ? "1" : "0";
766769
} else if (x instanceof Date) {
767-
return "'" + DataTypeUtils.DATE_FORMATTER.format(((Date) x).toLocalDate()) + "'";
770+
return QUOTE + DataTypeUtils.DATE_FORMATTER.format(((Date) x).toLocalDate()) + QUOTE;
768771
} else if (x instanceof LocalDate) {
769-
return "'" + DataTypeUtils.DATE_FORMATTER.format((LocalDate) x) + "'";
772+
return QUOTE + DataTypeUtils.DATE_FORMATTER.format((LocalDate) x) + QUOTE;
770773
} else if (x instanceof Time) {
771-
return "'" + TIME_FORMATTER.format(((Time) x).toLocalTime()) + "'";
774+
return QUOTE + TIME_FORMATTER.format(((Time) x).toLocalTime()) + QUOTE;
772775
} else if (x instanceof LocalTime) {
773-
return "'" + TIME_FORMATTER.format((LocalTime) x) + "'";
776+
return QUOTE + TIME_FORMATTER.format((LocalTime) x) + QUOTE;
774777
} else if (x instanceof Timestamp) {
775-
return "'" + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + "'";
778+
return QUOTE + DATETIME_FORMATTER.format(((Timestamp) x).toLocalDateTime()) + QUOTE;
776779
} else if (x instanceof LocalDateTime) {
777-
return "'" + DATETIME_FORMATTER.format((LocalDateTime) x) + "'";
780+
return QUOTE + DATETIME_FORMATTER.format((LocalDateTime) x) + QUOTE;
778781
} else if (x instanceof OffsetDateTime) {
779782
return encodeObject(((OffsetDateTime) x).toInstant());
780783
} else if (x instanceof ZonedDateTime) {
781784
return encodeObject(((ZonedDateTime) x).toInstant());
782785
} else if (x instanceof Instant) {
783786
return "fromUnixTimestamp64Nano(" + (((Instant) x).getEpochSecond() * 1_000_000_000L + ((Instant) x).getNano()) + ")";
784787
} else if (x instanceof InetAddress) {
785-
return "'" + ((InetAddress) x).getHostAddress() + "'";
788+
return QUOTE + ((InetAddress) x).getHostAddress() + QUOTE;
786789
} else if (x instanceof java.sql.Array) {
787790
StringBuilder listString = new StringBuilder();
788-
listString.append("[");
789-
int i = 0;
790-
for (Object item : (Object[]) ((Array) x).getArray()) {
791-
if (i > 0) {
792-
listString.append(", ");
793-
}
794-
listString.append(encodeObject(item));
795-
i++;
796-
}
797-
listString.append("]");
791+
listString.append('[');
792+
appendArrayElements((Object[]) ((Array) x).getArray(), listString);
793+
listString.append(']');
798794

799795
return listString.toString();
800796
} else if (x.getClass().isArray()) {
801797
StringBuilder listString = new StringBuilder();
802-
listString.append("[");
803-
804-
798+
listString.append('[');
805799
if (x.getClass().getComponentType().isPrimitive()) {
806800
int len = java.lang.reflect.Array.getLength(x);
807801
for (int i = 0; i < len; i++) {
808-
if (i > 0) {
809-
listString.append(", ");
810-
}
811-
listString.append(encodeObject(java.lang.reflect.Array.get(x, i)));
802+
listString.append(encodeObject(java.lang.reflect.Array.get(x, i))).append(',');
812803
}
804+
listString.setLength(listString.length() - 1);
813805
} else {
814-
int i = 0;
815-
for (Object item : (Object[]) x) {
816-
if (i > 0) {
817-
listString.append(", ");
818-
}
819-
listString.append(encodeObject(item));
820-
i++;
821-
}
806+
appendArrayElements((Object[]) x, listString);
822807
}
823-
listString.append("]");
808+
listString.append(']');
824809

825810
return listString.toString();
826811
} else if (x instanceof Collection) {
827812
StringBuilder listString = new StringBuilder();
828-
listString.append("[");
813+
listString.append('[');
829814
for (Object item : (Collection<?>) x) {
830-
listString.append(encodeObject(item)).append(", ");
831-
}
832-
if (listString.length() > 1) {
833-
listString.delete(listString.length() - 2, listString.length());
815+
listString.append(encodeObject(item)).append(',');
834816
}
835-
listString.append("]");
817+
listString.setLength(listString.length() - 1);
818+
listString.append(']');
836819

837820
return listString.toString();
838821
} else if (x instanceof Map) {
839822
Map<?, ?> tmpMap = (Map<?, ?>) x;
840823
StringBuilder mapString = new StringBuilder();
841-
mapString.append("{");
824+
mapString.append('{');
842825
for (Object key : tmpMap.keySet()) {
843-
mapString.append(encodeObject(key)).append(": ").append(encodeObject(tmpMap.get(key))).append(", ");
826+
mapString.append(encodeObject(key)).append(": ").append(encodeObject(tmpMap.get(key))).append(',');
844827
}
845828
if (!tmpMap.isEmpty())
846829
mapString.delete(mapString.length() - 2, mapString.length());
847-
mapString.append("}");
830+
mapString.append('}');
848831

849832
return mapString.toString();
850833
} else if (x instanceof Reader) {
@@ -853,35 +836,16 @@ private static String encodeObject(Object x, Long length) throws SQLException {
853836
return encodeCharacterStream((InputStream) x, length);
854837
} else if (x instanceof Object[]) {
855838
StringBuilder arrayString = new StringBuilder();
856-
arrayString.append("[");
857-
int i = 0;
858-
for (Object item : (Object[]) x) {
859-
if (i > 0) {
860-
arrayString.append(", ");
861-
}
862-
arrayString.append(encodeObject(item));
863-
i++;
864-
}
865-
arrayString.append("]");
866-
839+
arrayString.append('[');
840+
appendArrayElements((Object[]) x, arrayString);
841+
arrayString.append(']');
867842
return arrayString.toString();
868843
} else if (x instanceof Tuple) {
869-
StringBuilder tupleString = new StringBuilder();
870-
tupleString.append("(");
871-
Tuple t = (Tuple) x;
872-
Object [] values = t.getValues();
873-
int i = 0;
874-
for (Object item : values) {
875-
if (i > 0) {
876-
tupleString.append(", ");
877-
}
878-
tupleString.append(encodeObject(item));
879-
i++;
880-
}
881-
tupleString.append(")");
882-
return tupleString.toString();
844+
return arrayToTuple(((Tuple)x).getValues());
845+
} else if (x instanceof Struct) {
846+
return arrayToTuple(((Struct)x).getAttributes());
883847
} else if (x instanceof UUID) {
884-
return "'" + ((UUID) x).toString() + "'";
848+
return QUOTE + ((UUID) x).toString() + QUOTE;
885849
}
886850

887851
return SQLUtils.escapeSingleQuotes(x.toString()); //Escape single quotes
@@ -891,6 +855,21 @@ private static String encodeObject(Object x, Long length) throws SQLException {
891855
}
892856
}
893857

858+
private void appendArrayElements(Object[] array, StringBuilder sb) throws SQLException {
859+
for (Object item : array) {
860+
sb.append(encodeObject(item)).append(',');
861+
}
862+
sb.setLength(sb.length() - 1);
863+
}
864+
865+
private String arrayToTuple(Object[] array) throws SQLException {
866+
StringBuilder tupleString = new StringBuilder();
867+
tupleString.append('(');
868+
appendArrayElements(array, tupleString);
869+
tupleString.append(')');
870+
return tupleString.toString();
871+
}
872+
894873
private static String encodeCharacterStream(InputStream stream, Long length) throws SQLException {
895874
return encodeCharacterStream(new InputStreamReader(stream, StandardCharsets.UTF_8), length);
896875
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public boolean isIgnoreUnsupportedRequests() {
6161
* @param info - Driver and Client properties.
6262
*/
6363
public JdbcConfiguration(String url, Properties info) throws SQLException {
64-
this.disableFrameworkDetection = Boolean.parseBoolean(info.getProperty("disable_frameworks_detection", "false"));
64+
this.disableFrameworkDetection = info != null && Boolean.parseBoolean(info.getProperty("disable_frameworks_detection", "false"));
6565
this.clientProperties = new HashMap<>();
6666
this.driverProperties = new HashMap<>();
6767

jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.sql.ResultSet;
88
import java.sql.SQLException;
99
import java.sql.SQLFeatureNotSupportedException;
10+
import java.util.Arrays;
1011
import java.util.List;
1112
import java.util.Map;
1213

@@ -20,12 +21,26 @@ public Array(List<Object> list, String elementTypeName, int itemType) throws SQL
2021
if (list == null) {
2122
throw ExceptionUtils.toSqlState(new IllegalArgumentException("List cannot be null"));
2223
}
23-
24+
if (elementTypeName == null) {
25+
throw ExceptionUtils.toSqlState(new IllegalArgumentException("Array element type name cannot be null"));
26+
}
2427
this.array = list.toArray();
2528
this.type = itemType;
2629
this.elementTypeName = elementTypeName;
2730
}
2831

32+
public Array(String elementTypeName, int itemType, Object[] elements) throws SQLException {
33+
if (elements == null) {
34+
throw ExceptionUtils.toSqlState(new IllegalArgumentException("Array cannot be null"));
35+
}
36+
if (elementTypeName == null) {
37+
throw ExceptionUtils.toSqlState(new IllegalArgumentException("Array element type name cannot be null"));
38+
}
39+
this.array = elements;
40+
this.type = itemType;
41+
this.elementTypeName = elementTypeName;
42+
}
43+
2944
@Override
3045
public String getBaseTypeName() throws SQLException {
3146
return elementTypeName;

jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.clickhouse.client.ClickHouseServerForTest;
66
import com.clickhouse.client.api.Client;
77
import com.clickhouse.client.api.ClientConfigProperties;
8+
import com.clickhouse.client.api.DataTypeUtils;
89
import com.clickhouse.client.api.ServerException;
910
import com.clickhouse.client.api.internal.ServerSettings;
1011
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -28,8 +29,10 @@
2829
import java.sql.Timestamp;
2930
import java.time.LocalDateTime;
3031
import java.time.ZoneId;
32+
import java.time.temporal.TemporalAccessor;
3133
import java.util.Arrays;
3234
import java.util.Base64;
35+
import java.util.List;
3336
import java.util.Properties;
3437
import java.util.UUID;
3538

@@ -90,7 +93,6 @@ public void testCreateUnsupportedStatements() throws Throwable {
9093
() -> conn.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT),
9194
conn::setSavepoint,
9295
() -> conn.setSavepoint("save point"),
93-
() -> conn.createStruct("simple", null),
9496
};
9597

9698
for (Assert.ThrowingRunnable createStatement : createStatements) {
@@ -367,19 +369,23 @@ public static Object[][] setAndGetClientInfoTestDataProvider() {
367369

368370
@Test(groups = { "integration" })
369371
public void createArrayOfTest() throws SQLException {
370-
try (Connection conn = new ConnectionImpl(getEndpointString(), null)) {
371-
final String arrayType = "Array(Array(String))";
372+
try (Connection conn = getJdbcConnection()) {
373+
final String baseType = "Tuple(String, Int8)";
372374
final String tableName = "array_create_test";
375+
final String arrayType = "Array(Array(" + baseType + "))";
373376
try (Statement stmt = conn.createStatement()) {
374377
stmt.executeUpdate("CREATE TABLE " +tableName + " (v1 " + arrayType + ") ENGINE MergeTree ORDER BY ()");
375378

376379

377-
String[][] srcArray = new String[][] {
378-
new String[] {"v1"},
379-
new String[] {"v1", "v2"},
380-
new String[] {"v1", "v2", "v3"},
380+
Struct tuple1 = conn.createStruct(baseType, new Object[]{"v1", (byte)10});
381+
Struct tuple2 = conn.createStruct(baseType, new Object[]{"v2", (byte)20});
382+
383+
Struct[][] srcArray = new Struct[][] {
384+
new Struct[] { tuple1},
385+
new Struct[] { tuple1, tuple2},
381386
};
382-
Array arrayValue = conn.createArrayOf(arrayType, srcArray );
387+
388+
Array arrayValue = conn.createArrayOf("Tuple(String, Int8)", srcArray );
383389
try (PreparedStatement pStmt = conn.prepareStatement("INSERT INTO " + tableName + " (v1) VALUES (?)")) {
384390
pStmt.setArray(1, arrayValue);
385391
pStmt.executeUpdate();
@@ -390,9 +396,11 @@ public void createArrayOfTest() throws SQLException {
390396
try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) {
391397
Assert.assertTrue(rs.next());
392398
Array array1 = rs.getArray(1);
393-
rs.next();
394-
Array array2 = rs.getArray(1);
395-
Assert.assertEquals(array1, array2);
399+
Object[] elements = (Object[]) array1.getArray();
400+
Object[] storedTuple1 = (Object[]) ((List)elements[0]).get(0);
401+
Object[] storedTuple2 = (Object[]) ((List)elements[1]).get(1);
402+
Assert.assertEquals(storedTuple1, tuple1.getAttributes());
403+
Assert.assertEquals(storedTuple2, tuple2.getAttributes());
396404
}
397405
}
398406
}
@@ -419,10 +427,11 @@ public void testCreateStruct() throws SQLException {
419427

420428
try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) {
421429
Assert.assertTrue(rs.next());
422-
Struct structValue = (Struct) rs.getObject(1);
423-
Assert.assertEquals(structValue.getAttributes()[0], 120);
424-
Assert.assertEquals(structValue.getAttributes()[1], "test tuple value");
425-
Assert.assertEquals(structValue.getAttributes()[2], timePart);
430+
Object[] tuple = (Object[]) rs.getObject(1);
431+
Assert.assertEquals(tuple[0], (byte)120);
432+
Assert.assertEquals(tuple[1], "test tuple value");
433+
Assert.assertEquals(DataTypeUtils.DATETIME_WITH_NANOS_FORMATTER.format((TemporalAccessor) tuple[2]),
434+
timePart.toString());
426435
}
427436
}
428437
}

0 commit comments

Comments
 (0)