66import com .clickhouse .data .ClickHouseDataType ;
77import com .clickhouse .data .Tuple ;
88import com .clickhouse .data .format .BinaryStreamUtils ;
9+ import com .clickhouse .jdbc .PreparedStatementImpl ;
910import com .clickhouse .jdbc .types .Array ;
1011import com .google .common .collect .ImmutableMap ;
1112import org .slf4j .Logger ;
3132import java .util .List ;
3233import java .util .Map ;
3334import java .util .Set ;
35+ import java .util .Stack ;
3436import java .util .TreeMap ;
37+ import java .util .function .Function ;
3538import java .util .stream .Collectors ;
3639
3740public 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