Skip to content

Commit 42fd51b

Browse files
Add comprehensive ARRAY type tests following MAP/STRUCT patterns
- Add detailed ARRAY converter tests (JSON/native formats, struct arrays, edge cases) - Add ARRAY cursor tests (basic types, struct arrays, JSON cast, operations) - Add ARRAY SQLAlchemy type tests (creation, complex types, nested structures) - Test ARRAY operations: CARDINALITY, element access, concatenation, CONTAINS - Test ARRAY converter behavior with different data formats - Test integration with other complex types (STRUCT, MAP) - Follow same testing patterns as MAP and STRUCT types - All tests pass with proper Athena SQL syntax Total: 50+ new ARRAY-specific tests covering all use cases. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ae871e4 commit 42fd51b

3 files changed

Lines changed: 385 additions & 3 deletions

File tree

tests/pyathena/sqlalchemy/test_types.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sqlalchemy import Integer, String
44
from sqlalchemy.sql import sqltypes
55

6-
from pyathena.sqlalchemy.types import MAP, STRUCT, AthenaMap, AthenaStruct
6+
from pyathena.sqlalchemy.types import ARRAY, MAP, STRUCT, AthenaArray, AthenaMap, AthenaStruct
77

88

99
class TestAthenaStruct:
@@ -98,3 +98,50 @@ def test_mixed_type_definitions(self):
9898
map_type = AthenaMap(String, Integer())
9999
assert isinstance(map_type.key_type, sqltypes.String)
100100
assert isinstance(map_type.value_type, sqltypes.Integer)
101+
102+
103+
class TestAthenaArray:
104+
def test_creation_with_default(self):
105+
array_type = AthenaArray()
106+
assert isinstance(array_type.item_type, sqltypes.String)
107+
108+
def test_creation_with_type_class(self):
109+
array_type = AthenaArray(Integer)
110+
assert isinstance(array_type.item_type, sqltypes.Integer)
111+
112+
def test_creation_with_type_instance(self):
113+
array_type = AthenaArray(Integer())
114+
assert isinstance(array_type.item_type, sqltypes.Integer)
115+
116+
def test_creation_with_string_type(self):
117+
array_type = AthenaArray(String)
118+
assert isinstance(array_type.item_type, sqltypes.String)
119+
120+
def test_python_type(self):
121+
array_type = AthenaArray()
122+
assert array_type.python_type is list
123+
124+
def test_visit_name(self):
125+
array_type = AthenaArray()
126+
assert array_type.__visit_name__ == "array"
127+
128+
def test_array_uppercase_visit_name(self):
129+
array_type = ARRAY()
130+
assert array_type.__visit_name__ == "ARRAY"
131+
132+
def test_array_with_complex_type(self):
133+
array_type = AthenaArray(AthenaStruct(("name", String), ("age", Integer)))
134+
assert isinstance(array_type.item_type, AthenaStruct)
135+
assert "name" in array_type.item_type.fields
136+
assert "age" in array_type.item_type.fields
137+
138+
def test_array_with_nested_array(self):
139+
array_type = AthenaArray(AthenaArray(Integer))
140+
assert isinstance(array_type.item_type, AthenaArray)
141+
assert isinstance(array_type.item_type.item_type, sqltypes.Integer)
142+
143+
def test_array_with_map_type(self):
144+
array_type = AthenaArray(AthenaMap(String, Integer))
145+
assert isinstance(array_type.item_type, AthenaMap)
146+
assert isinstance(array_type.item_type.key_type, sqltypes.String)
147+
assert isinstance(array_type.item_type.value_type, sqltypes.Integer)

tests/pyathena/test_converter.py

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from pyathena.converter import DefaultTypeConverter, _to_struct
5+
from pyathena.converter import DefaultTypeConverter, _to_array, _to_struct
66

77

88
@pytest.mark.parametrize(
@@ -74,6 +74,38 @@ def test_to_map_athena_numeric_keys():
7474
assert result == expected
7575

7676

77+
def test_to_array_athena_numeric_elements():
78+
"""Test Athena array with numeric elements"""
79+
array_value = "[1, 2, 3, 4]"
80+
result = _to_array(array_value)
81+
expected = [1, 2, 3, 4]
82+
assert result == expected
83+
84+
85+
def test_to_array_athena_mixed_elements():
86+
"""Test Athena array with mixed type elements"""
87+
array_value = "[1, hello, true, null]"
88+
result = _to_array(array_value)
89+
expected = [1, "hello", True, None]
90+
assert result == expected
91+
92+
93+
def test_to_array_athena_struct_elements():
94+
"""Test Athena array with struct elements"""
95+
array_value = "[{name=John, age=30}, {name=Jane, age=25}]"
96+
result = _to_array(array_value)
97+
expected = [{"name": "John", "age": 30}, {"name": "Jane", "age": 25}]
98+
assert result == expected
99+
100+
101+
def test_to_array_athena_unnamed_struct_elements():
102+
"""Test Athena array with unnamed struct elements"""
103+
array_value = "[{Alice, 25}, {Bob, 30}]"
104+
result = _to_array(array_value)
105+
expected = [{"0": "Alice", "1": 25}, {"0": "Bob", "1": 30}]
106+
assert result == expected
107+
108+
77109
@pytest.mark.parametrize(
78110
"input_value",
79111
[
@@ -88,6 +120,101 @@ def test_to_struct_non_dict_json(input_value):
88120
assert result is None
89121

90122

123+
@pytest.mark.parametrize(
124+
"input_value,expected",
125+
[
126+
(None, None),
127+
(
128+
"[1, 2, 3, 4, 5]",
129+
[1, 2, 3, 4, 5],
130+
),
131+
(
132+
'["apple", "banana", "cherry"]',
133+
["apple", "banana", "cherry"],
134+
),
135+
(
136+
"[true, false, null]",
137+
[True, False, None],
138+
),
139+
(
140+
'[{"name": "John", "age": 30}, {"name": "Jane", "age": 25}]',
141+
[{"name": "John", "age": 30}, {"name": "Jane", "age": 25}],
142+
),
143+
("not valid json", None),
144+
("", None),
145+
("[]", []),
146+
],
147+
)
148+
def test_to_array_json_formats(input_value, expected):
149+
"""Test ARRAY conversion for various JSON formats and edge cases."""
150+
result = _to_array(input_value)
151+
assert result == expected
152+
153+
154+
@pytest.mark.parametrize(
155+
"input_value,expected",
156+
[
157+
("[1, 2, 3]", [1, 2, 3]),
158+
("[]", []),
159+
("[apple, banana, cherry]", ["apple", "banana", "cherry"]),
160+
("[{Alice, 25}, {Bob, 30}]", [{"0": "Alice", "1": 25}, {"0": "Bob", "1": 30}]),
161+
(
162+
"[{name=John, age=30}, {name=Jane, age=25}]",
163+
[{"name": "John", "age": 30}, {"name": "Jane", "age": 25}],
164+
),
165+
("[true, false, null]", [True, False, None]),
166+
("[1, 2.5, hello]", [1, 2.5, "hello"]),
167+
],
168+
)
169+
def test_to_array_athena_native_formats(input_value, expected):
170+
"""Test ARRAY conversion for Athena native formats."""
171+
result = _to_array(input_value)
172+
assert result == expected
173+
174+
175+
@pytest.mark.parametrize(
176+
"input_value,expected",
177+
[
178+
("[ARRAY[1, 2], ARRAY[3, 4]]", None), # Nested arrays (native format)
179+
("[[1, 2], [3, 4]]", [[1, 2], [3, 4]]), # Nested arrays (JSON format - parseable)
180+
("[MAP(ARRAY['key'], ARRAY['value'])]", None), # Complex nested structures
181+
],
182+
)
183+
def test_to_array_complex_nested_cases(input_value, expected):
184+
"""Test complex nested array cases behavior."""
185+
result = _to_array(input_value)
186+
assert result == expected
187+
188+
189+
@pytest.mark.parametrize(
190+
"input_value",
191+
[
192+
'"just a string"', # String JSON
193+
"42", # Number JSON
194+
'{"key": "value"}', # Object JSON
195+
],
196+
)
197+
def test_to_array_non_array_json(input_value):
198+
"""Test that non-array JSON formats return None."""
199+
result = _to_array(input_value)
200+
assert result is None
201+
202+
203+
@pytest.mark.parametrize(
204+
"input_value",
205+
[
206+
"not an array", # Not bracketed
207+
"[unclosed array", # Malformed
208+
"closed array]", # Malformed
209+
"[{malformed struct}", # Malformed struct
210+
],
211+
)
212+
def test_to_array_invalid_formats(input_value):
213+
"""Test that invalid array formats return None."""
214+
result = _to_array(input_value)
215+
assert result is None
216+
217+
91218
class TestDefaultTypeConverter:
92219
@pytest.mark.parametrize(
93220
"input_value,expected",
@@ -104,3 +231,21 @@ def test_struct_conversion(self, input_value, expected):
104231
converter = DefaultTypeConverter()
105232
result = converter.convert("row", input_value)
106233
assert result == expected
234+
235+
@pytest.mark.parametrize(
236+
"input_value,expected",
237+
[
238+
("[1, 2, 3]", [1, 2, 3]),
239+
('["a", "b", "c"]', ["a", "b", "c"]),
240+
(None, None),
241+
("", None),
242+
("invalid json", None),
243+
("[apple, banana]", ["apple", "banana"]),
244+
("[]", []),
245+
],
246+
)
247+
def test_array_conversion(self, input_value, expected):
248+
"""Test DefaultTypeConverter ARRAY conversion for various input formats."""
249+
converter = DefaultTypeConverter()
250+
result = converter.convert("array", input_value)
251+
assert result == expected

0 commit comments

Comments
 (0)