37
37
38
38
import java .io .Serializable ;
39
39
import java .lang .reflect .Array ;
40
+ import java .math .BigInteger ;
40
41
import java .time .OffsetDateTime ;
41
42
import java .util .ArrayList ;
42
43
import java .util .Arrays ;
44
+ import java .util .Collection ;
43
45
import java .util .Collections ;
46
+ import java .util .Comparator ;
47
+ import java .util .HashMap ;
44
48
import java .util .LinkedList ;
45
49
import java .util .List ;
50
+ import java .util .Map ;
46
51
import java .util .Objects ;
52
+ import java .util .Set ;
47
53
import java .util .TimeZone ;
48
54
49
55
/**
@@ -65,6 +71,7 @@ public final class ClickHouseColumn implements Serializable {
65
71
private static final String KEYWORD_OBJECT = ClickHouseDataType .Object .name ();
66
72
private static final String KEYWORD_MAP = ClickHouseDataType .Map .name ();
67
73
private static final String KEYWORD_NESTED = ClickHouseDataType .Nested .name ();
74
+ private static final String KEYWORD_VARIANT = ClickHouseDataType .Variant .name ();
68
75
69
76
private int columnCount ;
70
77
private int columnIndex ;
@@ -92,6 +99,14 @@ public final class ClickHouseColumn implements Serializable {
92
99
93
100
private ClickHouseValue template ;
94
101
102
+ private Map <Class <?>, Integer > classToVariantOrdNumMap ;
103
+
104
+ private Map <Class <?>, Integer > arrayToVariantOrdNumMap ;
105
+
106
+ private Map <Class <?>, Integer > mapKeyToVariantOrdNumMap ;
107
+ private Map <Class <?>, Integer > mapValueToVariantOrdNumMap ;
108
+
109
+
95
110
private static ClickHouseColumn update (ClickHouseColumn column ) {
96
111
column .enumConstants = ClickHouseEnum .EMPTY ;
97
112
int size = column .parameters .size ();
@@ -273,6 +288,9 @@ private static ClickHouseColumn update(ClickHouseColumn column) {
273
288
case Nothing :
274
289
column .template = ClickHouseEmptyValue .INSTANCE ;
275
290
break ;
291
+ case Variant :
292
+ column .template = ClickHouseTupleValue .of ();
293
+ break ;
276
294
default :
277
295
break ;
278
296
}
@@ -398,7 +416,8 @@ protected static int readColumn(String args, int startIndex, int len, String nam
398
416
fixedLength = false ;
399
417
estimatedLength ++;
400
418
} else if (args .startsWith (matchedKeyword = KEYWORD_TUPLE , i )
401
- || args .startsWith (matchedKeyword = KEYWORD_OBJECT , i )) {
419
+ || args .startsWith (matchedKeyword = KEYWORD_OBJECT , i )
420
+ || args .startsWith (matchedKeyword = KEYWORD_VARIANT , i )) {
402
421
int index = args .indexOf ('(' , i + matchedKeyword .length ());
403
422
if (index < i ) {
404
423
throw new IllegalArgumentException (ERROR_MISSING_NESTED_TYPE );
@@ -410,12 +429,22 @@ protected static int readColumn(String args, int startIndex, int len, String nam
410
429
if (c == ')' ) {
411
430
break ;
412
431
} else if (c != ',' && !Character .isWhitespace (c )) {
432
+ String columnName = "" ;
413
433
i = readColumn (args , i , endIndex , "" , nestedColumns );
414
434
}
415
435
}
416
436
if (nestedColumns .isEmpty ()) {
417
437
throw new IllegalArgumentException ("Tuple should have at least one nested column" );
418
438
}
439
+
440
+ List <ClickHouseDataType > variantDataTypes = new ArrayList <>();
441
+ if (matchedKeyword .equals (KEYWORD_VARIANT )) {
442
+ nestedColumns .sort (Comparator .comparing (o -> o .getDataType ().name ()));
443
+ nestedColumns .forEach (c -> {
444
+ c .columnName = "v." + c .getDataType ().name ();
445
+ variantDataTypes .add (c .dataType );
446
+ });
447
+ }
419
448
column = new ClickHouseColumn (ClickHouseDataType .valueOf (matchedKeyword ), name ,
420
449
args .substring (startIndex , endIndex + 1 ), nullable , lowCardinality , null , nestedColumns );
421
450
for (ClickHouseColumn n : nestedColumns ) {
@@ -424,6 +453,39 @@ protected static int readColumn(String args, int startIndex, int len, String nam
424
453
fixedLength = false ;
425
454
}
426
455
}
456
+ column .classToVariantOrdNumMap = ClickHouseDataType .buildVariantMapping (variantDataTypes );
457
+
458
+ for (int ordNum = 0 ; ordNum < nestedColumns .size (); ordNum ++) {
459
+ ClickHouseColumn nestedColumn = nestedColumns .get (ordNum );
460
+ if (nestedColumn .getDataType () == ClickHouseDataType .Array ) {
461
+ Set <Class <?>> classSet = ClickHouseDataType .DATA_TYPE_TO_CLASS .get (nestedColumn .arrayBaseColumn .dataType );
462
+ if (classSet != null ) {
463
+ if (column .arrayToVariantOrdNumMap == null ) {
464
+ column .arrayToVariantOrdNumMap = new HashMap <>();
465
+ }
466
+ for (Class <?> c : classSet ) {
467
+ column .arrayToVariantOrdNumMap .put (c , ordNum );
468
+ }
469
+ }
470
+ } else if (nestedColumn .getDataType () == ClickHouseDataType .Map ) {
471
+ Set <Class <?>> keyClassSet = ClickHouseDataType .DATA_TYPE_TO_CLASS .get (nestedColumn .getKeyInfo ().getDataType ());
472
+ Set <Class <?>> valueClassSet = ClickHouseDataType .DATA_TYPE_TO_CLASS .get (nestedColumn .getValueInfo ().getDataType ());
473
+ if (keyClassSet != null && valueClassSet != null ) {
474
+ if (column .mapKeyToVariantOrdNumMap == null ) {
475
+ column .mapKeyToVariantOrdNumMap = new HashMap <>();
476
+ }
477
+ if (column .mapValueToVariantOrdNumMap == null ) {
478
+ column .mapValueToVariantOrdNumMap = new HashMap <>();
479
+ }
480
+ for (Class <?> c : keyClassSet ) {
481
+ column .mapKeyToVariantOrdNumMap .put (c , ordNum );
482
+ }
483
+ for (Class <?> c : valueClassSet ) {
484
+ column .mapValueToVariantOrdNumMap .put (c , ordNum );
485
+ }
486
+ }
487
+ }
488
+ }
427
489
}
428
490
429
491
if (column == null ) {
@@ -627,6 +689,52 @@ public boolean isAggregateFunction() {
627
689
628
690
}
629
691
692
+ public int getVariantOrdNum (Object value ) {
693
+ if (value != null && value .getClass ().isArray ()) {
694
+ // TODO: add cache by value class
695
+ Class <?> c = value .getClass ();
696
+ while (c .isArray ()) {
697
+ c = c .getComponentType ();
698
+ }
699
+ return arrayToVariantOrdNumMap .getOrDefault (c , -1 );
700
+ } else if (value != null && value instanceof List <?>) {
701
+ // TODO: add cache by instance of the list
702
+ Object tmpV = ((List ) value ).get (0 );
703
+ Class <?> valueClass = tmpV .getClass ();
704
+ while (tmpV instanceof List <?>) {
705
+ tmpV = ((List ) tmpV ).get (0 );
706
+ valueClass = tmpV .getClass ();
707
+ }
708
+ return arrayToVariantOrdNumMap .getOrDefault (valueClass , -1 );
709
+ } else if (value != null && value instanceof Map <?,?>) {
710
+ // TODO: add cache by instance of map
711
+ Map <?, ?> map = (Map <?, ?>) value ;
712
+ if (!map .isEmpty ()) {
713
+ for (Map .Entry <?, ?> e : map .entrySet ()) {
714
+ if (e .getValue () != null ) {
715
+ int keyOrdNum = mapKeyToVariantOrdNumMap .getOrDefault (e .getKey ().getClass (), -1 );
716
+ int valueOrdNum = mapValueToVariantOrdNumMap .getOrDefault (e .getValue ().getClass (), -1 );
717
+
718
+ if (keyOrdNum == valueOrdNum ) {
719
+ return valueOrdNum ; // exact match
720
+ } else if (keyOrdNum != -1 && valueOrdNum != -1 ) {
721
+ if (ClickHouseDataType .DATA_TYPE_TO_CLASS .get (nested .get (keyOrdNum ).getValueInfo ().getDataType ()).contains (e .getValue ().getClass ())){
722
+ return keyOrdNum ; // can write to map found by key class because values are compatible
723
+ } else {
724
+ return valueOrdNum ;
725
+ }
726
+ }
727
+
728
+ break ;
729
+ }
730
+ }
731
+ }
732
+ return -1 ;
733
+ } else {
734
+ return classToVariantOrdNumMap .getOrDefault (value .getClass (), -1 );
735
+ }
736
+ }
737
+
630
738
public boolean isArray () {
631
739
return dataType == ClickHouseDataType .Array ;
632
740
}
0 commit comments