3737
3838import java .io .Serializable ;
3939import java .lang .reflect .Array ;
40+ import java .math .BigInteger ;
4041import java .time .OffsetDateTime ;
4142import java .util .ArrayList ;
4243import java .util .Arrays ;
44+ import java .util .Collection ;
4345import java .util .Collections ;
46+ import java .util .Comparator ;
47+ import java .util .HashMap ;
4448import java .util .LinkedList ;
4549import java .util .List ;
50+ import java .util .Map ;
4651import java .util .Objects ;
52+ import java .util .Set ;
4753import java .util .TimeZone ;
4854
4955/**
@@ -65,6 +71,7 @@ public final class ClickHouseColumn implements Serializable {
6571 private static final String KEYWORD_OBJECT = ClickHouseDataType .Object .name ();
6672 private static final String KEYWORD_MAP = ClickHouseDataType .Map .name ();
6773 private static final String KEYWORD_NESTED = ClickHouseDataType .Nested .name ();
74+ private static final String KEYWORD_VARIANT = ClickHouseDataType .Variant .name ();
6875
6976 private int columnCount ;
7077 private int columnIndex ;
@@ -92,6 +99,14 @@ public final class ClickHouseColumn implements Serializable {
9299
93100 private ClickHouseValue template ;
94101
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+
95110 private static ClickHouseColumn update (ClickHouseColumn column ) {
96111 column .enumConstants = ClickHouseEnum .EMPTY ;
97112 int size = column .parameters .size ();
@@ -273,6 +288,9 @@ private static ClickHouseColumn update(ClickHouseColumn column) {
273288 case Nothing :
274289 column .template = ClickHouseEmptyValue .INSTANCE ;
275290 break ;
291+ case Variant :
292+ column .template = ClickHouseTupleValue .of ();
293+ break ;
276294 default :
277295 break ;
278296 }
@@ -398,7 +416,8 @@ protected static int readColumn(String args, int startIndex, int len, String nam
398416 fixedLength = false ;
399417 estimatedLength ++;
400418 } 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 )) {
402421 int index = args .indexOf ('(' , i + matchedKeyword .length ());
403422 if (index < i ) {
404423 throw new IllegalArgumentException (ERROR_MISSING_NESTED_TYPE );
@@ -410,12 +429,22 @@ protected static int readColumn(String args, int startIndex, int len, String nam
410429 if (c == ')' ) {
411430 break ;
412431 } else if (c != ',' && !Character .isWhitespace (c )) {
432+ String columnName = "" ;
413433 i = readColumn (args , i , endIndex , "" , nestedColumns );
414434 }
415435 }
416436 if (nestedColumns .isEmpty ()) {
417437 throw new IllegalArgumentException ("Tuple should have at least one nested column" );
418438 }
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+ }
419448 column = new ClickHouseColumn (ClickHouseDataType .valueOf (matchedKeyword ), name ,
420449 args .substring (startIndex , endIndex + 1 ), nullable , lowCardinality , null , nestedColumns );
421450 for (ClickHouseColumn n : nestedColumns ) {
@@ -424,6 +453,39 @@ protected static int readColumn(String args, int startIndex, int len, String nam
424453 fixedLength = false ;
425454 }
426455 }
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+ }
427489 }
428490
429491 if (column == null ) {
@@ -627,6 +689,52 @@ public boolean isAggregateFunction() {
627689
628690 }
629691
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+
630738 public boolean isArray () {
631739 return dataType == ClickHouseDataType .Array ;
632740 }
0 commit comments