Skip to content

Commit a320a07

Browse files
committed
Merge branch 'main' into jdbc_impl_getResultSet_in_Array
2 parents da0de18 + e251400 commit a320a07

File tree

2 files changed

+223
-91
lines changed

2 files changed

+223
-91
lines changed

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

Lines changed: 197 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.clickhouse.data.ClickHouseDataType;
77
import com.clickhouse.data.Tuple;
88
import com.clickhouse.data.format.BinaryStreamUtils;
9+
import com.clickhouse.jdbc.PreparedStatementImpl;
910
import com.clickhouse.jdbc.types.Array;
1011
import com.google.common.collect.ImmutableMap;
1112
import org.slf4j.Logger;
@@ -31,7 +32,9 @@
3132
import java.util.List;
3233
import java.util.Map;
3334
import java.util.Set;
35+
import java.util.Stack;
3436
import java.util.TreeMap;
37+
import java.util.function.Function;
3538
import java.util.stream.Collectors;
3639

3740
public class JdbcUtils {
@@ -85,6 +88,8 @@ private static Map<ClickHouseDataType, SQLType> generateTypeMap() {
8588
map.put(ClickHouseDataType.LineString, JDBCType.ARRAY);
8689
map.put(ClickHouseDataType.MultiPolygon, JDBCType.ARRAY);
8790
map.put(ClickHouseDataType.MultiLineString, JDBCType.ARRAY);
91+
map.put(ClickHouseDataType.Tuple, JDBCType.OTHER);
92+
map.put(ClickHouseDataType.Nothing, JDBCType.OTHER);
8893
return ImmutableMap.copyOf(map);
8994
}
9095

@@ -169,6 +174,8 @@ private static Map<ClickHouseDataType, Class<?>> getDataTypeClassMap() {
169174
default:
170175
map.put(e.getKey(), Object.class);
171176
}
177+
} else if (e.getValue().equals(JDBCType.STRUCT)) {
178+
map.put(e.getKey(), Object.class);
172179
} else {
173180
map.put(e.getKey(), SQL_TYPE_TO_CLASS_MAP.get(e.getValue()));
174181
}
@@ -217,6 +224,27 @@ public static Class<?> convertToJavaClass(ClickHouseDataType clickhouseType) {
217224
return DATA_TYPE_CLASS_MAP.get(clickhouseType);
218225
}
219226

227+
private static Class<?> unwrapPrimitiveType(Class<?> type) {
228+
if (type == int.class) {
229+
return Integer.class;
230+
} else if (type == long.class) {
231+
return Long.class;
232+
} else if (type == boolean.class) {
233+
return Boolean.class;
234+
} else if (type == float.class) {
235+
return Float.class;
236+
} else if (type == double.class) {
237+
return Double.class;
238+
} else if (type == char.class) {
239+
return Character.class;
240+
} else if (type == byte.class) {
241+
return Byte.class;
242+
} else if (type == short.class) {
243+
return Short.class;
244+
}
245+
return type;
246+
}
247+
220248
public static Object convert(Object value, Class<?> type) throws SQLException {
221249
return convert(value, type, null);
222250
}
@@ -225,74 +253,102 @@ public static Object convert(Object value, Class<?> type, ClickHouseColumn colum
225253
if (value == null || type == null) {
226254
return value;
227255
}
256+
257+
type = unwrapPrimitiveType(type);
258+
if (type.isInstance(value)) {
259+
return value;
260+
}
261+
262+
if (value instanceof List<?>) {
263+
List<?> listValue = (List<?>) value;
264+
if (type != java.sql.Array.class) {
265+
return convertList(listValue, type, column.getArrayNestedLevel());
266+
}
267+
268+
if (column != null && column.getArrayBaseColumn() != null) {
269+
ClickHouseDataType baseType = column.getArrayBaseColumn().getDataType();
270+
Object[] convertedValues = convertList(listValue, convertToJavaClass(baseType),
271+
column.getArrayNestedLevel());
272+
return new Array(column, convertedValues);
273+
}
274+
275+
// base type is unknown. all objects should be converted
276+
return new Array(column, listValue.toArray());
277+
}
278+
279+
if (value.getClass().isArray()) {
280+
if (type == java.sql.Array.class) {
281+
return new Array(column, arrayToObjectArray(value));
282+
} else if (type == Tuple.class) {
283+
return new Tuple(true, value);
284+
}
285+
}
286+
287+
if (type == java.sql.Array.class && value instanceof BinaryStreamReader.ArrayValue) {
288+
BinaryStreamReader.ArrayValue arrayValue = (BinaryStreamReader.ArrayValue) value;
289+
290+
if (column != null && column.getArrayBaseColumn() != null) {
291+
ClickHouseDataType baseType = column.getArrayBaseColumn().getDataType();
292+
Object[] convertedValues = convertArray(arrayValue.getArray(), convertToJavaClass(baseType),
293+
column.getArrayNestedLevel());
294+
return new Array(column, convertedValues);
295+
}
296+
297+
return new Array(column, arrayValue.getArrayOfObjects());
298+
}
299+
300+
return convertObject(value, type);
301+
}
302+
303+
public static Object convertObject(Object value, Class<?> type) throws SQLException {
304+
if (value == null || type == null) {
305+
return value;
306+
}
228307
try {
229-
if (type.isInstance(value)) {
230-
return value;
231-
} else if (type != java.sql.Array.class && value instanceof List<?>) {
232-
return convertList((List<?>) value, type);
233-
} else if (type == String.class) {
308+
if (type == String.class) {
234309
return value.toString();
235-
} else if (type == Boolean.class || type == boolean.class) {
310+
} else if (type == Boolean.class) {
236311
String str = value.toString();
237312
return !("false".equalsIgnoreCase(str) || "0".equalsIgnoreCase(str));
238-
} else if (type == Byte.class || type == byte.class) {
313+
} else if (type == Byte.class) {
239314
return Byte.parseByte(value.toString());
240-
} else if (type == Short.class || type == short.class) {
315+
} else if (type == Short.class) {
241316
return Short.parseShort(value.toString());
242-
} else if (type == Integer.class || type == int.class) {
317+
} else if (type == Integer.class) {
243318
return Integer.parseInt(value.toString());
244-
} else if (type == Long.class || type == long.class) {
319+
} else if (type == Long.class) {
245320
return Long.parseLong(value.toString());
246-
} else if (type == Float.class || type == float.class) {
321+
} else if (type == Float.class) {
247322
return Float.parseFloat(value.toString());
248-
} else if (type == Double.class || type == double.class) {
323+
} else if (type == Double.class) {
249324
return Double.parseDouble(value.toString());
250325
} else if (type == java.math.BigDecimal.class) {
251326
return new java.math.BigDecimal(value.toString());
252-
} else if (type == byte[].class) {
253-
return value.toString().getBytes();
254-
} else if (type == LocalDate.class && value instanceof TemporalAccessor) {
255-
return LocalDate.from((TemporalAccessor) value);
256-
} else if (type == LocalDateTime.class && value instanceof TemporalAccessor) {
257-
return LocalDateTime.from((TemporalAccessor) value);
258-
} else if (type == OffsetDateTime.class && value instanceof TemporalAccessor) {
259-
return OffsetDateTime.from((TemporalAccessor) value);
260-
} else if (type == ZonedDateTime.class && value instanceof TemporalAccessor) {
261-
return ZonedDateTime.from((TemporalAccessor) value);
262-
} else if (type == Instant.class && value instanceof TemporalAccessor) {
263-
return Instant.from((TemporalAccessor) value);
264-
} else if (type == Date.class && value instanceof TemporalAccessor) {
265-
return Date.valueOf(LocalDate.from((TemporalAccessor) value));
266-
} else if (type == java.sql.Timestamp.class && value instanceof TemporalAccessor) {
267-
return java.sql.Timestamp.valueOf(LocalDateTime.from((TemporalAccessor) value));
268-
} else if (type == java.sql.Time.class && value instanceof TemporalAccessor) {
269-
return java.sql.Time.valueOf(LocalTime.from((TemporalAccessor) value));
270-
} else if (type == java.sql.Array.class && value instanceof BinaryStreamReader.ArrayValue) {//It's cleaner to use getList but this handles the more generic getObject
271-
BinaryStreamReader.ArrayValue arrayValue = (BinaryStreamReader.ArrayValue) value;
272-
if (column != null && column.getArrayBaseColumn() != null) {
273-
ClickHouseDataType baseType = column.getArrayBaseColumn().getDataType();
274-
Object[] convertedValues = convertArray(arrayValue.getArrayOfObjects(), JdbcUtils.convertToJavaClass(baseType));
275-
return new Array(column, convertedValues);
276-
}
277-
return new Array(column, arrayValue.getArrayOfObjects());
278-
} else if (type == java.sql.Array.class && value instanceof List<?>) {
279-
if (column != null && column.getArrayBaseColumn() != null) {
280-
ClickHouseDataType baseType = column.getArrayBaseColumn().getDataType();
281-
Object[] convertedValues = convertList((List<?>) value, JdbcUtils.convertToJavaClass(baseType));
282-
return new Array(column, convertedValues);
327+
} else if (value instanceof TemporalAccessor) {
328+
TemporalAccessor temporalValue = (TemporalAccessor) value;
329+
if (type == LocalDate.class) {
330+
return LocalDate.from(temporalValue);
331+
} else if (type == LocalDateTime.class) {
332+
return LocalDateTime.from(temporalValue);
333+
} else if (type == OffsetDateTime.class) {
334+
return OffsetDateTime.from(temporalValue);
335+
} else if (type == ZonedDateTime.class) {
336+
return ZonedDateTime.from(temporalValue);
337+
} else if (type == Instant.class) {
338+
return Instant.from(temporalValue);
339+
} else if (type == Date.class) {
340+
return Date.valueOf(LocalDate.from(temporalValue));
341+
} else if (type == java.sql.Timestamp.class) {
342+
return java.sql.Timestamp.valueOf(LocalDateTime.from(temporalValue));
343+
} else if (type == java.sql.Time.class) {
344+
return java.sql.Time.valueOf(LocalTime.from(temporalValue));
283345
}
284-
// base type is unknown. all objects should be converted
285-
return new Array(column, ((List<?>) value).toArray());
286-
} else if (type == java.sql.Array.class && value.getClass().isArray()) {
287-
return new Array(column, arrayToObjectArray(value));
288346
} else if (type == Inet4Address.class && value instanceof Inet6Address) {
289347
// Convert Inet6Address to Inet4Address
290348
return InetAddressConverter.convertToIpv4((InetAddress) value);
291349
} else if (type == Inet6Address.class && value instanceof Inet4Address) {
292350
// Convert Inet4Address to Inet6Address
293351
return InetAddressConverter.convertToIpv6((InetAddress) value);
294-
} else if (type == Tuple.class && value.getClass().isArray()) {
295-
return new Tuple(true, value);
296352
}
297353
} catch (Exception e) {
298354
throw new SQLException("Failed to convert from " + value.getClass().getName() + " to " + type.getName(), ExceptionUtils.SQL_STATE_DATA_EXCEPTION, e);
@@ -301,32 +357,103 @@ public static Object convert(Object value, Class<?> type, ClickHouseColumn colum
301357
throw new SQLException("Unsupported conversion from " + value.getClass().getName() + " to " + type.getName(), ExceptionUtils.SQL_STATE_DATA_EXCEPTION);
302358
}
303359

304-
public static Object[] convertList(List<?> values, Class<?> type) throws SQLException {
360+
public static <T> T[] convertList(List<?> values, Class<T> type, int dimensions) throws SQLException {
305361
if (values == null) {
306362
return null;
307363
}
308-
if (values.isEmpty()) {
309-
return new Object[0];
310-
}
311364

312-
Object[] convertedValues = new Object[values.size()];
313-
for (int i = 0; i < values.size(); i++) {
314-
convertedValues[i] = convert(values.get(i), type);
365+
int[] arrayDimensions = new int[dimensions];
366+
arrayDimensions[0] = values.size();
367+
T[] convertedValues = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions);
368+
Stack<ArrayProcessingCursor> stack = new Stack<>();
369+
stack.push(new ArrayProcessingCursor(convertedValues, values, values.size()));
370+
371+
while (!stack.isEmpty()) {
372+
ArrayProcessingCursor cursor = stack.pop();
373+
374+
for (int i = 0; i < cursor.size; i++) {
375+
Object value = cursor.getValue(i);
376+
if (value == null) {
377+
continue; // no need to set null value
378+
} else if (value instanceof List<?>) {
379+
List<?> srcList = (List<?>) value;
380+
arrayDimensions = new int[Math.max(dimensions - stack.size() - 1, 1)];
381+
arrayDimensions[0] = srcList.size();
382+
T[] targetArray = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions);
383+
stack.push(new ArrayProcessingCursor(targetArray, value, srcList.size()));
384+
java.lang.reflect.Array.set(cursor.targetArray, i, targetArray);
385+
} else {
386+
java.lang.reflect.Array.set(cursor.targetArray, i, convert(value, type));
387+
}
388+
}
315389
}
390+
316391
return convertedValues;
317392
}
318393

319-
public static Object[] convertArray(Object[] values, Class<?> type) throws SQLException {
320-
if (values == null || type == null) {
321-
return values;
394+
/**
395+
* Convert array to java array and all its elements
396+
* @param values
397+
* @param type
398+
* @param dimensions
399+
* @return
400+
* @param <T>
401+
* @throws SQLException
402+
*/
403+
public static <T> T[] convertArray(Object values, Class<T> type, int dimensions) throws SQLException {
404+
if (values == null) {
405+
return null;
322406
}
323-
Object[] convertedValues = new Object[values.length];
324-
for (int i = 0; i < values.length; i++) {
325-
convertedValues[i] = convert(values[i], type);
407+
408+
int[] arrayDimensions = new int[dimensions];
409+
arrayDimensions[0] = java.lang.reflect.Array.getLength(values);
410+
T[] convertedValues = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions);
411+
Stack<ArrayProcessingCursor> stack = new Stack<>();
412+
stack.push(new ArrayProcessingCursor(convertedValues, values, arrayDimensions[0]));
413+
414+
while (!stack.isEmpty()) {
415+
ArrayProcessingCursor cursor = stack.pop();
416+
417+
for (int i = 0; i < cursor.size; i++) {
418+
Object value = cursor.getValue(i);
419+
if (value == null) {
420+
continue; // no need to set null value
421+
} else if (value.getClass().isArray()) {
422+
arrayDimensions = new int[Math.max(dimensions - stack.size() - 1, 1)];
423+
arrayDimensions[0] = java.lang.reflect.Array.getLength(value);
424+
T[] targetArray = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions);
425+
stack.push(new ArrayProcessingCursor(targetArray, value, arrayDimensions[0]));
426+
java.lang.reflect.Array.set(cursor.targetArray, i, targetArray);
427+
} else {
428+
java.lang.reflect.Array.set(cursor.targetArray, i, convert(value, type));
429+
}
430+
}
326431
}
432+
327433
return convertedValues;
328434
}
329435

436+
private static final class ArrayProcessingCursor {
437+
private final Object targetArray;
438+
private final int size;
439+
private final Function<Integer, Object> valueGetter;
440+
441+
public ArrayProcessingCursor(Object targetArray, Object srcArray, int size) {
442+
this.targetArray = targetArray;
443+
this.size = size;
444+
if (srcArray instanceof List<?>) {
445+
List<?> list = (List<?>) srcArray;
446+
this.valueGetter = list::get;
447+
} else {
448+
this.valueGetter = (i) -> java.lang.reflect.Array.get(srcArray, i);
449+
}
450+
}
451+
452+
public Object getValue(int i) {
453+
return valueGetter.apply(i);
454+
}
455+
}
456+
330457
private static Object[] arrayToObjectArray(Object array) {
331458
if (array == null) {
332459
return null;
@@ -340,56 +467,56 @@ private static Object[] arrayToObjectArray(Object array) {
340467

341468
if (array instanceof byte[]) {
342469
byte[] src = (byte[]) array;
343-
Object[] dst = new Object[src.length];
470+
Byte[] dst = new Byte[src.length];
344471
for (int i = 0; i < src.length; i++) {
345472
dst[i] = src[i];
346473
}
347474
return dst;
348475
} else if (array instanceof short[]) {
349476
short[] src = (short[]) array;
350-
Object[] dst = new Object[src.length];
477+
Short[] dst = new Short[src.length];
351478
for (int i = 0; i < src.length; i++) {
352479
dst[i] = src[i];
353480
}
354481
return dst;
355482
} else if (array instanceof int[]) {
356483
int[] src = (int[]) array;
357-
Object[] dst = new Object[src.length];
484+
Integer[] dst = new Integer[src.length];
358485
for (int i = 0; i < src.length; i++) {
359486
dst[i] = src[i];
360487
}
361488
return dst;
362489
} else if (array instanceof long[]) {
363490
long[] src = (long[]) array;
364-
Object[] dst = new Object[src.length];
491+
Long[] dst = new Long[src.length];
365492
for (int i = 0; i < src.length; i++) {
366493
dst[i] = src[i];
367494
}
368495
return dst;
369496
} else if (array instanceof float[]) {
370497
float[] src = (float[]) array;
371-
Object[] dst = new Object[src.length];
498+
Float[] dst = new Float[src.length];
372499
for (int i = 0; i < src.length; i++) {
373500
dst[i] = src[i];
374501
}
375502
return dst;
376503
} else if (array instanceof double[]) {
377504
double[] src = (double[]) array;
378-
Object[] dst = new Object[src.length];
505+
Double[] dst = new Double[src.length];
379506
for (int i = 0; i < src.length; i++) {
380507
dst[i] = src[i];
381508
}
382509
return dst;
383510
} else if (array instanceof char[]) {
384511
char[] src = (char[]) array;
385-
Object[] dst = new Object[src.length];
512+
Character[] dst = new Character[src.length];
386513
for (int i = 0; i < src.length; i++) {
387514
dst[i] = src[i];
388515
}
389516
return dst;
390517
} else if (array instanceof boolean[]) {
391518
boolean[] src = (boolean[]) array;
392-
Object[] dst = new Object[src.length];
519+
Boolean[] dst = new Boolean[src.length];
393520
for (int i = 0; i < src.length; i++) {
394521
dst[i] = src[i];
395522
}

0 commit comments

Comments
 (0)