Skip to content

Commit 87fff0d

Browse files
test: enhance struct type support test coverage
Add comprehensive test cases for STRUCT type functionality: - Add integration tests in test_base.py to verify struct type recognition - Update one_row_complex tests to expect AthenaStruct instead of String - Add tests for both struct<> and row<> type parsing in _get_column_type - Expand converter tests with edge cases - Test empty strings, non-dict JSON, and invalid JSON handling - Document behavior with Athena's native struct format {a=1, b=2} - Add compiler tests for error conditions and single field structs - Add type tests for mixed field definitions and error handling These tests ensure robust struct support and maintain backward compatibility while providing comprehensive coverage of the new functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d034520 commit 87fff0d

4 files changed

Lines changed: 76 additions & 2 deletions

File tree

tests/pyathena/sqlalchemy/test_base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,9 @@ def test_reflect_select(self, engine):
275275
assert isinstance(one_row_complex.c.col_binary.type, types.BINARY)
276276
assert isinstance(one_row_complex.c.col_array.type, types.String)
277277
assert isinstance(one_row_complex.c.col_map.type, types.String)
278-
assert isinstance(one_row_complex.c.col_struct.type, types.String)
278+
# With struct support, col_struct should now be recognized as AthenaStruct
279+
from pyathena.sqlalchemy.types import AthenaStruct
280+
assert isinstance(one_row_complex.c.col_struct.type, AthenaStruct)
279281
assert isinstance(
280282
one_row_complex.c.col_decimal.type,
281283
types.DECIMAL,
@@ -321,7 +323,10 @@ def test_get_column_type(self, engine):
321323
assert isinstance(dialect._get_column_type("binary"), types.BINARY)
322324
assert isinstance(dialect._get_column_type("array<integer>"), types.String)
323325
assert isinstance(dialect._get_column_type("map<int, int>"), types.String)
324-
assert isinstance(dialect._get_column_type("struct<a: int, b: int>"), types.String)
326+
# With struct support, struct types should be recognized as AthenaStruct
327+
from pyathena.sqlalchemy.types import AthenaStruct
328+
assert isinstance(dialect._get_column_type("struct<a: int, b: int>"), AthenaStruct)
329+
assert isinstance(dialect._get_column_type("row<name: string, age: int>"), AthenaStruct)
325330
decimal_with_args = dialect._get_column_type("decimal(10,1)")
326331
assert isinstance(decimal_with_args, types.DECIMAL)
327332
assert decimal_with_args.precision == 10

tests/pyathena/sqlalchemy/test_compiler.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,16 @@ def test_visit_struct_uppercase(self):
3131
assert "id INTEGER" in result
3232
assert "title STRING" in result or "title VARCHAR" in result
3333
assert result.endswith(")")
34+
35+
def test_visit_struct_no_fields_attribute(self):
36+
# Test struct type without fields attribute
37+
compiler = AthenaTypeCompiler()
38+
struct_type = type('MockStruct', (), {})()
39+
result = compiler.visit_struct(struct_type)
40+
assert result == "ROW()"
41+
42+
def test_visit_struct_single_field(self):
43+
compiler = AthenaTypeCompiler()
44+
struct_type = AthenaStruct(("name", String))
45+
result = compiler.visit_struct(struct_type)
46+
assert result == "ROW(name STRING)" or result == "ROW(name VARCHAR)"

tests/pyathena/sqlalchemy/test_types.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,19 @@ def test_visit_name(self):
4848
def test_struct_uppercase_visit_name(self):
4949
struct_type = STRUCT()
5050
assert struct_type.__visit_name__ == "STRUCT"
51+
52+
def test_empty_struct(self):
53+
struct_type = AthenaStruct()
54+
assert len(struct_type.fields) == 0
55+
56+
def test_mixed_field_definitions(self):
57+
struct_type = AthenaStruct("name", ("age", Integer), ("active", String()))
58+
assert len(struct_type.fields) == 3
59+
assert isinstance(struct_type.fields["name"], sqltypes.String)
60+
assert isinstance(struct_type.fields["age"], sqltypes.Integer)
61+
assert isinstance(struct_type.fields["active"], sqltypes.String)
62+
63+
def test_field_access_nonexistent_key(self):
64+
struct_type = AthenaStruct(("name", String))
65+
with pytest.raises(KeyError):
66+
struct_type["nonexistent"]

tests/pyathena/test_converter.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ def test_to_struct_invalid_json():
2828
assert result is None
2929

3030

31+
def test_to_struct_empty_string():
32+
result = _to_struct("")
33+
assert result is None
34+
35+
36+
def test_to_struct_non_dict_json():
37+
# Arrays and other non-dict JSON should return None
38+
array_json = '[1, 2, 3]'
39+
result = _to_struct(array_json)
40+
assert result is None
41+
42+
string_json = '"just a string"'
43+
result = _to_struct(string_json)
44+
assert result is None
45+
46+
number_json = '42'
47+
result = _to_struct(number_json)
48+
assert result is None
49+
50+
3151
class TestDefaultTypeConverter:
3252
def test_struct_conversion(self):
3353
converter = DefaultTypeConverter()
@@ -40,3 +60,23 @@ def test_struct_conversion_none(self):
4060
converter = DefaultTypeConverter()
4161
result = converter.convert("row", None)
4262
assert result is None
63+
64+
def test_struct_conversion_empty_string(self):
65+
converter = DefaultTypeConverter()
66+
result = converter.convert("row", "")
67+
assert result is None
68+
69+
def test_struct_conversion_invalid_json(self):
70+
converter = DefaultTypeConverter()
71+
result = converter.convert("row", "invalid json")
72+
assert result is None
73+
74+
def test_struct_conversion_athena_format(self):
75+
"""Test conversion of actual Athena struct format like {a=1, b=2}"""
76+
converter = DefaultTypeConverter()
77+
# This is how Athena actually returns struct data in some cases
78+
# For now, our converter expects JSON format, but this test documents the behavior
79+
result = converter.convert("row", "{a=1, b=2}")
80+
# Currently returns None because it's not valid JSON
81+
# This could be enhanced in the future to parse Athena's struct format
82+
assert result is None

0 commit comments

Comments
 (0)