@@ -759,204 +759,183 @@ def test_fetchall(self, dict_cursor):
759759class TestComplexDataTypes :
760760 """Test complex data types (STRUCT, ARRAY, MAP) with actual Athena queries."""
761761
762- def test_struct_types (self , cursor ):
763- """Test various STRUCT type scenarios to understand Athena's behavior."""
764- test_cases = [
765- # Basic struct
762+ @pytest .mark .parametrize (
763+ "query,description" ,
764+ [
766765 ("SELECT ROW('John', 30) AS simple_struct" , "simple_struct" ),
767- # Named struct fields
768766 (
769767 "SELECT CAST(ROW('Alice', 25) AS ROW(name VARCHAR, age INTEGER)) AS named_struct" ,
770768 "named_struct" ,
771769 ),
772- # Struct with special characters
773770 ("SELECT ROW('Hello, world', 'x=y+1') AS special_chars_struct" , "special_chars_struct" ),
774- # Struct with quotes
775771 ("SELECT ROW('He said \" hello\" ', 'It''s working') AS quotes_struct" , "quotes_struct" ),
776- # Struct with NULL values
777772 ("SELECT ROW('Alice', NULL, 'active') AS null_struct" , "null_struct" ),
778- # Nested struct
779773 (
780774 "SELECT ROW(ROW('John', 30), ROW('Engineer', 'Tech')) AS nested_struct" ,
781775 "nested_struct" ,
782776 ),
783- # Struct as JSON (using CAST AS JSON)
784777 ("SELECT CAST(ROW('Alice', 25, 'Hello, world') AS JSON) AS json_struct" , "json_struct" ),
785- ]
786-
787- _logger .info ("=== STRUCT Type Test Results ===" )
788- for query , description in test_cases :
789- cursor .execute (query )
790- result = cursor .fetchone ()
791- struct_value = result [0 ]
792- _logger .info (f"{ description } : { struct_value !r} (type: { type (struct_value ).__name__ } )" )
793-
794- # Validate struct value and converter behavior
795- assert struct_value is not None , f"STRUCT value should not be None for { description } "
796-
797- # Test struct conversion behavior
798- if isinstance (struct_value , str ):
799- converted = _to_struct (struct_value )
800- _logger .info (f"{ description } : Converted { struct_value !r} -> { converted !r} " )
801- # For string structs, conversion should succeed or return None for complex cases
802- if converted is not None :
803- assert isinstance (converted , dict ), (
804- f"Converted struct should be dict for { description } "
805- )
806- elif isinstance (struct_value , dict ):
807- # Already converted by the cursor converter
808- _logger .info (f"{ description } : Already converted to dict: { struct_value !r} " )
809- else :
810- # Log unexpected types for debugging but don't fail
811- _logger .warning (
812- f"{ description } : Unexpected type { type (struct_value ).__name__ } : "
813- f"{ struct_value !r} "
778+ ],
779+ )
780+ def test_struct_types (self , cursor , query , description ):
781+ """Test various STRUCT type scenarios to understand Athena's behavior."""
782+ _logger .info (f"=== STRUCT Type Test: { description } ===" )
783+ cursor .execute (query )
784+ result = cursor .fetchone ()
785+ struct_value = result [0 ]
786+ _logger .info (f"{ description } : { struct_value !r} (type: { type (struct_value ).__name__ } )" )
787+
788+ # Validate struct value and converter behavior
789+ assert struct_value is not None , f"STRUCT value should not be None for { description } "
790+
791+ # Test struct conversion behavior
792+ if isinstance (struct_value , str ):
793+ converted = _to_struct (struct_value )
794+ _logger .info (f"{ description } : Converted { struct_value !r} -> { converted !r} " )
795+ # For string structs, conversion should succeed or return None for complex cases
796+ if converted is not None :
797+ assert isinstance (converted , dict ), (
798+ f"Converted struct should be dict for { description } "
814799 )
800+ elif isinstance (struct_value , dict ):
801+ # Already converted by the cursor converter
802+ _logger .info (f"{ description } : Already converted to dict: { struct_value !r} " )
803+ else :
804+ # Log unexpected types for debugging but don't fail
805+ _logger .warning (
806+ f"{ description } : Unexpected type { type (struct_value ).__name__ } : { struct_value !r} "
807+ )
815808
816- def test_array_types (self , cursor ):
817- """Test various ARRAY type scenarios."""
818- test_cases = [
819- # Simple array
809+ @pytest .mark .parametrize (
810+ "query,description" ,
811+ [
820812 ("SELECT ARRAY[1, 2, 3, 4, 5] AS simple_array" , "simple_array" ),
821- # String array
822813 ("SELECT ARRAY['apple', 'banana', 'cherry'] AS string_array" , "string_array" ),
823- # Array with special characters
824814 (
825815 "SELECT ARRAY['Hello, world', 'x=y+1', 'It''s working'] AS special_array" ,
826816 "special_array" ,
827817 ),
828- # Array of structs
829818 ("SELECT ARRAY[ROW('Alice', 25), ROW('Bob', 30)] AS struct_array" , "struct_array" ),
830- # Nested arrays
831819 ("SELECT ARRAY[ARRAY[1, 2], ARRAY[3, 4]] AS nested_array" , "nested_array" ),
832- # Array as JSON (wrapped in object - top-level arrays not supported)
833820 (
834821 "SELECT CAST(MAP(ARRAY['data'], ARRAY[ARRAY['Alice', 'Bob']]) AS JSON) "
835822 "AS array_json" ,
836823 "array_json" ,
837824 ),
838- ]
839-
840- _logger .info ("=== ARRAY Type Test Results ===" )
841- for query , description in test_cases :
842- cursor .execute (query )
843- result = cursor .fetchone ()
844- array_value = result [0 ]
845- _logger .info (f"{ description } : { array_value !r} (type: { type (array_value ).__name__ } )" )
825+ ],
826+ )
827+ def test_array_types (self , cursor , query , description ):
828+ """Test various ARRAY type scenarios."""
829+ _logger .info (f"=== ARRAY Type Test: { description } ===" )
830+ cursor .execute (query )
831+ result = cursor .fetchone ()
832+ array_value = result [0 ]
833+ _logger .info (f"{ description } : { array_value !r} (type: { type (array_value ).__name__ } )" )
846834
847- # Validate array value
848- assert array_value is not None , f"ARRAY value should not be None for { description } "
849- _logger .info (f"{ description } : Array value type { type (array_value ).__name__ } " )
835+ # Validate array value
836+ assert array_value is not None , f"ARRAY value should not be None for { description } "
837+ _logger .info (f"{ description } : Array value type { type (array_value ).__name__ } " )
850838
851- def test_map_types (self , cursor ):
852- """Test various MAP type scenarios."""
853- test_cases = [
854- # Simple map
839+ @pytest .mark .parametrize (
840+ "query,description" ,
841+ [
855842 (
856843 "SELECT MAP(ARRAY[1, 2, 3], ARRAY['one', 'two', 'three']) AS simple_map" ,
857844 "simple_map" ,
858845 ),
859- # String key map
860846 (
861- (
862- "SELECT MAP(ARRAY['name', 'age', 'city'], "
863- "ARRAY['John', '30', 'Tokyo']) AS string_map"
864- ),
847+ "SELECT MAP(ARRAY['name', 'age', 'city'], ARRAY['John', '30', 'Tokyo']) "
848+ "AS string_map" ,
865849 "string_map" ,
866850 ),
867- # Map with special characters
868851 (
869- (
870- "SELECT MAP(ARRAY['msg', 'formula'], "
871- "ARRAY['Hello, world', 'x=y+1']) AS special_map"
872- ),
852+ "SELECT MAP(ARRAY['msg', 'formula'], ARRAY['Hello, world', 'x=y+1']) "
853+ "AS special_map" ,
873854 "special_map" ,
874855 ),
875- # Map with struct values
876856 (
877- (
878- "SELECT MAP(ARRAY['person1', 'person2'], "
879- "ARRAY[ROW('Alice', 25), ROW('Bob', 30)]) AS struct_value_map"
880- ),
857+ "SELECT CAST(MAP(ARRAY['person1', 'person2'], "
858+ "ARRAY[ROW('Alice', 25), ROW('Bob', 30)]) AS JSON) AS struct_value_map" ,
881859 "struct_value_map" ,
882860 ),
883- # Map as JSON (using CAST AS JSON)
884861 (
885862 "SELECT CAST(MAP(ARRAY['name', 'age'], ARRAY['Alice', '25']) AS JSON) AS json_map" ,
886863 "json_map" ,
887864 ),
888- ]
889-
890- _logger .info ("=== MAP Type Test Results ===" )
891- for query , description in test_cases :
892- cursor .execute (query )
893- result = cursor .fetchone ()
894- map_value = result [0 ]
895- _logger .info (f"{ description } : { map_value !r} (type: { type (map_value ).__name__ } )" )
896-
897- # Validate map value and converter behavior
898- assert map_value is not None , f"MAP value should not be None for { description } "
899-
900- # Test map conversion behavior
901- if isinstance (map_value , str ):
865+ ],
866+ )
867+ def test_map_types (self , cursor , query , description ):
868+ """Test various MAP type scenarios."""
869+ _logger .info (f"=== MAP Type Test: { description } ===" )
870+ cursor .execute (query )
871+ result = cursor .fetchone ()
872+ map_value = result [0 ]
873+ _logger .info (f"{ description } : { map_value !r} (type: { type (map_value ).__name__ } )" )
874+
875+ # Validate map value and converter behavior
876+ assert map_value is not None , f"MAP value should not be None for { description } "
877+
878+ # Test map conversion behavior
879+ if isinstance (map_value , str ):
880+ # For complex MAP structures, string is expected (JSON or native format)
881+ if "ROW(" in map_value or "ARRAY[" in map_value :
882+ # Complex structure, expect string format
883+ _logger .info (f"{ description } : Complex MAP kept as string: { map_value !r} " )
884+ else :
885+ # Simple MAP, try conversion
902886 converted = _to_map (map_value )
903887 _logger .info (f"{ description } : Converted { map_value !r} -> { converted !r} " )
904- # For string maps, conversion should succeed or return None for complex cases
905888 if converted is not None :
906889 assert isinstance (converted , dict ), (
907890 f"Converted map should be dict for { description } "
908891 )
909- elif isinstance (map_value , dict ):
910- # Already converted by the cursor converter
911- _logger .info (f"{ description } : Already converted to dict: { map_value !r} " )
912- else :
913- # Log unexpected types for debugging but don't fail
914- _logger .warning (
915- f"{ description } : Unexpected type { type (map_value ).__name__ } : { map_value !r} "
916- )
892+ elif isinstance (map_value , dict ):
893+ # Already converted by the cursor converter
894+ _logger .info (f"{ description } : Already converted to dict: { map_value !r} " )
895+ else :
896+ # Log unexpected types for debugging but don't fail
897+ _logger .warning (
898+ f"{ description } : Unexpected type { type (map_value ).__name__ } : { map_value !r} "
899+ )
917900
918- def test_complex_combinations (self , cursor ):
919- """Test complex combinations of data types."""
920- test_cases = [
921- # Struct containing array and map (using JSON conversion for complex structures)
901+ @pytest .mark .parametrize (
902+ "query,description" ,
903+ [
922904 (
923905 "SELECT CAST(ROW(ARRAY[1, 2, 3], MAP(ARRAY['a', 'b'], ARRAY[1, 2])) AS JSON) "
924906 "AS struct_with_collections" ,
925907 "struct_with_collections" ,
926908 ),
927- # Array of maps (using JSON conversion)
928909 (
929910 "SELECT CAST(ARRAY[MAP(ARRAY['name'], ARRAY['Alice']), "
930911 "MAP(ARRAY['name'], ARRAY['Bob'])] AS JSON) AS array_of_maps" ,
931912 "array_of_maps" ,
932913 ),
933- # Map with array values (using JSON conversion)
934914 (
935915 "SELECT CAST(MAP(ARRAY['numbers', 'letters'], "
936916 "ARRAY[ARRAY['1', '2', '3'], ARRAY['a', 'b', 'c']]) AS JSON) AS map_with_arrays" ,
937917 "map_with_arrays" ,
938918 ),
939- ]
940-
941- _logger .info ("=== Complex Combinations Test Results ===" )
942- for query , description in test_cases :
943- cursor .execute (query )
944- result = cursor .fetchone ()
945- complex_value = result [0 ]
946- _logger .info (f"{ description } : { complex_value !r} (type: { type (complex_value ).__name__ } )" )
947-
948- # For JSON cast results, expect string values that can be parsed as JSON
949- if isinstance (complex_value , str ):
950- try :
951- # Test that the JSON string can be parsed
952- parsed = json .loads (complex_value )
953- _logger .info (f" Parsed JSON: { parsed !r} " )
954- assert parsed is not None , f"Parsed JSON should not be None for { description } "
955- except json .JSONDecodeError as e :
956- raise AssertionError (f"JSON parsing failed for { description } : { e } " ) from e
957- else :
958- # If it's not a string, it should still be a valid value (not None)
959- assert complex_value is not None , (
960- f"Complex value should not be None for { description } "
961- )
962- _logger .info (f"{ description } : Complex value type { type (complex_value ).__name__ } " )
919+ ],
920+ )
921+ def test_complex_combinations (self , cursor , query , description ):
922+ """Test complex combinations of data types."""
923+ _logger .info (f"=== Complex Combination Test: { description } ===" )
924+ cursor .execute (query )
925+ result = cursor .fetchone ()
926+ complex_value = result [0 ]
927+ _logger .info (f"{ description } : { complex_value !r} (type: { type (complex_value ).__name__ } )" )
928+
929+ # For JSON cast results, expect string values that can be parsed as JSON
930+ if isinstance (complex_value , str ):
931+ try :
932+ # Test that the JSON string can be parsed
933+ parsed = json .loads (complex_value )
934+ _logger .info (f" Parsed JSON: { parsed !r} " )
935+ assert parsed is not None , f"Parsed JSON should not be None for { description } "
936+ except json .JSONDecodeError as e :
937+ raise AssertionError (f"JSON parsing failed for { description } : { e } " ) from e
938+ else :
939+ # If it's not a string, it should still be a valid value (not None)
940+ assert complex_value is not None , f"Complex value should not be None for { description } "
941+ _logger .info (f"{ description } : Complex value type { type (complex_value ).__name__ } " )
0 commit comments