5656import java .time .Instant ;
5757import java .time .LocalDate ;
5858import java .time .LocalDateTime ;
59+ import java .time .OffsetDateTime ;
5960import java .time .ZoneId ;
6061import java .time .ZonedDateTime ;
62+ import java .time .format .DateTimeParseException ;
63+ import java .util .ArrayList ;
6164import java .util .Base64 ;
6265import java .util .HashMap ;
66+ import java .util .LinkedHashMap ;
6367import java .util .List ;
6468import java .util .Map ;
6569import java .util .Objects ;
@@ -634,6 +638,9 @@ public void unpackArray(List<ColumnValue> values) {
634638 ColumnType childType = columnType .getChildTypes ().get (0 );
635639 Object arrayObject = value ;
636640 try {
641+ if (arrayObject instanceof String ) {
642+ arrayObject = new ComplexValueParser ((String ) arrayObject ).parseArray (childType );
643+ }
637644 if (arrayObject instanceof java .sql .Array ) {
638645 arrayObject = ((java .sql .Array ) arrayObject ).getArray ();
639646 }
@@ -655,7 +662,12 @@ public void unpackArray(List<ColumnValue> values) {
655662
656663 @ Override
657664 public void unpackMap (List <ColumnValue > keys , List <ColumnValue > values ) {
658- Map <?, ?> map = (Map <?, ?>) value ;
665+ Object mapObject = value ;
666+ if (mapObject instanceof String ) {
667+ mapObject = new ComplexValueParser ((String ) mapObject ).parseMap (
668+ columnType .getChildTypes ().get (0 ), columnType .getChildTypes ().get (1 ));
669+ }
670+ Map <?, ?> map = (Map <?, ?>) mapObject ;
659671 ColumnType keyType = columnType .getChildTypes ().get (0 );
660672 ColumnType valueType = columnType .getChildTypes ().get (1 );
661673 for (Map .Entry <?, ?> entry : map .entrySet ()) {
@@ -668,6 +680,22 @@ public void unpackMap(List<ColumnValue> keys, List<ColumnValue> values) {
668680 public void unpackStruct (List <Integer > structFieldIndex , List <ColumnValue > values ) {
669681 List <ColumnType > childTypes = columnType .getChildTypes ();
670682 try {
683+ if (value instanceof String ) {
684+ List <Object > fieldValues = new ComplexValueParser ((String ) value ).parseStruct (columnType );
685+ for (Integer fieldIndex : structFieldIndex ) {
686+ values .add (new ResultSetColumnValue (fieldValues .get (fieldIndex ),
687+ childTypes .get (fieldIndex ), timeZone ));
688+ }
689+ return ;
690+ }
691+ if (value instanceof List <?>) {
692+ List <?> fieldValues = (List <?>) value ;
693+ for (Integer fieldIndex : structFieldIndex ) {
694+ values .add (new ResultSetColumnValue (fieldValues .get (fieldIndex ),
695+ childTypes .get (fieldIndex ), timeZone ));
696+ }
697+ return ;
698+ }
671699 if (value instanceof Struct ) {
672700 Struct struct = (Struct ) value ;
673701 for (Integer fieldIndex : structFieldIndex ) {
@@ -704,7 +732,192 @@ private Instant asInstant() {
704732 if (value instanceof java .util .Date ) {
705733 return ((java .util .Date ) value ).toInstant ();
706734 }
735+ if (value instanceof CharSequence ) {
736+ String text = normalizeTemporalText (value .toString ());
737+ try {
738+ return OffsetDateTime .parse (text ).toInstant ();
739+ } catch (DateTimeParseException e ) {
740+ // fall through
741+ }
742+ try {
743+ return ZonedDateTime .parse (text ).toInstant ();
744+ } catch (DateTimeParseException e ) {
745+ // fall through
746+ }
747+ try {
748+ return Instant .parse (text );
749+ } catch (DateTimeParseException e ) {
750+ // fall through
751+ }
752+ try {
753+ return LocalDateTime .parse (text ).atZone (timeZone ).toInstant ();
754+ } catch (DateTimeParseException e ) {
755+ // fall through
756+ }
757+ try {
758+ return LocalDate .parse (text ).atStartOfDay (timeZone ).toInstant ();
759+ } catch (DateTimeParseException e ) {
760+ throw new IllegalStateException ("Unsupported temporal string value: " + text , e );
761+ }
762+ }
707763 throw new IllegalStateException ("Unsupported temporal value type: " + value .getClass ().getName ());
708764 }
765+
766+ private String normalizeTemporalText (String text ) {
767+ String normalized = text .trim ();
768+ if (normalized .length () > 10 && normalized .charAt (10 ) == ' ' ) {
769+ return normalized .substring (0 , 10 ) + "T" + normalized .substring (11 );
770+ }
771+ return normalized ;
772+ }
773+ }
774+
775+ private static final class ComplexValueParser {
776+ private final String text ;
777+ private int position ;
778+
779+ private ComplexValueParser (String text ) {
780+ this .text = text == null ? "" : text ;
781+ }
782+
783+ private List <Object > parseArray (ColumnType childType ) {
784+ skipLeadingSpaces ();
785+ expect ('[' );
786+ List <Object > values = new ArrayList <>();
787+ skipLeadingSpaces ();
788+ if (consumeIf (']' )) {
789+ return values ;
790+ }
791+ while (true ) {
792+ values .add (parseValue (childType , ',' , ']' ));
793+ skipLeadingSpaces ();
794+ if (consumeIf (',' )) {
795+ skipLeadingSpaces ();
796+ continue ;
797+ }
798+ expect (']' );
799+ return values ;
800+ }
801+ }
802+
803+ private Map <Object , Object > parseMap (ColumnType keyType , ColumnType valueType ) {
804+ skipLeadingSpaces ();
805+ expect ('{' );
806+ Map <Object , Object > values = new LinkedHashMap <>();
807+ skipLeadingSpaces ();
808+ if (consumeIf ('}' )) {
809+ return values ;
810+ }
811+ while (true ) {
812+ Object key = parseValue (keyType , ':' );
813+ expect (':' );
814+ Object value = parseValue (valueType , ',' , '}' );
815+ values .put (key , value );
816+ skipLeadingSpaces ();
817+ if (consumeIf (',' )) {
818+ skipLeadingSpaces ();
819+ continue ;
820+ }
821+ expect ('}' );
822+ return values ;
823+ }
824+ }
825+
826+ private List <Object > parseStruct (ColumnType structType ) {
827+ skipLeadingSpaces ();
828+ expect ('{' );
829+ List <Object > values = new ArrayList <>();
830+ for (int i = 0 ; i < structType .getChildTypes ().size (); i ++) {
831+ values .add (null );
832+ }
833+ skipLeadingSpaces ();
834+ if (consumeIf ('}' )) {
835+ return values ;
836+ }
837+ while (true ) {
838+ String fieldName = asString (parseScalar (':' ));
839+ int fieldIndex = structType .getChildNames ().indexOf (fieldName );
840+ if (fieldIndex < 0 ) {
841+ throw new IllegalStateException ("Unknown struct field '" + fieldName + "' in value: " + text );
842+ }
843+ expect (':' );
844+ values .set (fieldIndex , parseValue (structType .getChildTypes ().get (fieldIndex ), ',' , '}' ));
845+ skipLeadingSpaces ();
846+ if (consumeIf (',' )) {
847+ skipLeadingSpaces ();
848+ continue ;
849+ }
850+ expect ('}' );
851+ return values ;
852+ }
853+ }
854+
855+ private Object parseValue (ColumnType type , char ... terminators ) {
856+ skipLeadingSpaces ();
857+ if (isNullToken (terminators )) {
858+ position += 4 ;
859+ return null ;
860+ }
861+ if (type .isArray ()) {
862+ return parseArray (type .getChildTypes ().get (0 ));
863+ }
864+ if (type .isMap ()) {
865+ return parseMap (type .getChildTypes ().get (0 ), type .getChildTypes ().get (1 ));
866+ }
867+ if (type .isStruct ()) {
868+ return parseStruct (type );
869+ }
870+ return parseScalar (terminators );
871+ }
872+
873+ private Object parseScalar (char ... terminators ) {
874+ int start = position ;
875+ while (position < text .length () && !isTerminator (text .charAt (position ), terminators )) {
876+ position ++;
877+ }
878+ return text .substring (start , position );
879+ }
880+
881+ private boolean isNullToken (char ... terminators ) {
882+ if (!text .regionMatches (true , position , "NULL" , 0 , 4 )) {
883+ return false ;
884+ }
885+ int end = position + 4 ;
886+ return end >= text .length () || isTerminator (text .charAt (end ), terminators );
887+ }
888+
889+ private boolean isTerminator (char current , char ... terminators ) {
890+ for (char terminator : terminators ) {
891+ if (current == terminator ) {
892+ return true ;
893+ }
894+ }
895+ return false ;
896+ }
897+
898+ private void skipLeadingSpaces () {
899+ while (position < text .length () && text .charAt (position ) == ' ' ) {
900+ position ++;
901+ }
902+ }
903+
904+ private boolean consumeIf (char expected ) {
905+ if (position < text .length () && text .charAt (position ) == expected ) {
906+ position ++;
907+ return true ;
908+ }
909+ return false ;
910+ }
911+
912+ private void expect (char expected ) {
913+ if (!consumeIf (expected )) {
914+ throw new IllegalStateException ("Expected '" + expected + "' at position "
915+ + position + " in value: " + text );
916+ }
917+ }
918+
919+ private String asString (Object value ) {
920+ return value == null ? null : String .valueOf (value );
921+ }
709922 }
710923}
0 commit comments