Skip to content

Commit 15aec51

Browse files
committed
fix: Updates get_transitive_field_schemas and adds tests
1 parent 3f3ab72 commit 15aec51

File tree

2 files changed

+133
-1
lines changed

2 files changed

+133
-1
lines changed

sqlalchemy_bigquery/_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _get_transitive_schema_fields(fields):
8383
results = []
8484
for field in fields:
8585
results += [field]
86-
if field.field_type in STRUCT_FIELD_TYPES:
86+
if field.field_type in STRUCT_FIELD_TYPES and field.mode != "REPEATED":
8787
sub_fields = [
8888
SchemaField.from_api_repr(
8989
dict(f.to_api_repr(), name=f"{field.name}.{f.name}")

tests/unit/test__types.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import pytest
2+
from google.cloud.bigquery.schema import SchemaField
3+
4+
from sqlalchemy_bigquery._types import _get_transitive_schema_fields, STRUCT_FIELD_TYPES
5+
6+
def create_fut(name, field_type, mode="NULLABLE", sub_fields=None):
7+
"""
8+
Helper function to create a SchemaField object for testing.
9+
`sub_fields` should be a list of already created SchemaField objects.
10+
"""
11+
api_repr = {
12+
"name": name,
13+
"type": field_type,
14+
"mode": mode,
15+
"fields": [sf.to_api_repr() for sf in sub_fields] if sub_fields else [],
16+
}
17+
return SchemaField.from_api_repr(api_repr)
18+
19+
20+
test_cases = [
21+
(
22+
"STRUCT field, not REPEATED, with sub-fields, should recurse",
23+
[
24+
create_fut("s1", "STRUCT", "NULLABLE", sub_fields=[
25+
create_fut("child1", "STRING", "NULLABLE")
26+
])
27+
],
28+
["s1", "s1.child1"],
29+
),
30+
(
31+
"RECORD field (alias for STRUCT), not REPEATED, with sub-fields, should recurse",
32+
[
33+
create_fut("r1", "RECORD", "NULLABLE", sub_fields=[
34+
create_fut("child_r1", "INTEGER", "NULLABLE")
35+
])
36+
],
37+
["r1", "r1.child_r1"],
38+
),
39+
(
40+
"STRUCT field, REPEATED, with sub-fields, should NOT recurse",
41+
[
42+
create_fut("s2", "STRUCT", "REPEATED", sub_fields=[
43+
create_fut("child2", "STRING", "NULLABLE")
44+
])
45+
],
46+
["s2"],
47+
),
48+
(
49+
"Non-STRUCT field (STRING), not REPEATED, should NOT recurse",
50+
[
51+
create_fut("f1", "STRING", "NULLABLE")
52+
],
53+
["f1"],
54+
),
55+
(
56+
"Non-STRUCT field (INTEGER), REPEATED, should NOT recurse",
57+
[
58+
create_fut("f2", "INTEGER", "REPEATED")
59+
],
60+
["f2"],
61+
),
62+
(
63+
"Deeply nested STRUCT, not REPEATED, should recurse fully",
64+
[
65+
create_fut("s_outer", "STRUCT", "NULLABLE", sub_fields=[
66+
create_fut("s_inner1", "STRUCT", "NULLABLE", sub_fields=[
67+
create_fut("s_leaf1", "STRING", "NULLABLE")
68+
]),
69+
create_fut("s_sibling", "INTEGER", "NULLABLE"),
70+
create_fut("s_inner2_repeated_struct", "STRUCT", "REPEATED", sub_fields=[
71+
create_fut("s_leaf2_ignored", "BOOLEAN", "NULLABLE") # This sub-field should be ignored
72+
]),
73+
])
74+
],
75+
["s_outer", "s_outer.s_inner1", "s_outer.s_inner1.s_leaf1", "s_outer.s_sibling", "s_outer.s_inner2_repeated_struct"],
76+
),
77+
(
78+
"STRUCT field, not REPEATED, but no sub-fields, should not error and not recurse further",
79+
[
80+
create_fut("s3", "STRUCT", "NULLABLE", sub_fields=[])
81+
],
82+
["s3"],
83+
),
84+
(
85+
"Multiple top-level fields with mixed conditions",
86+
[
87+
create_fut("id", "INTEGER", "REQUIRED"),
88+
create_fut("user_profile", "STRUCT", "NULLABLE", sub_fields=[
89+
create_fut("name", "STRING", "NULLABLE"),
90+
create_fut("addresses", "RECORD", "REPEATED", sub_fields=[ # addresses is REPEATED STRUCT
91+
create_fut("street", "STRING", "NULLABLE"), # This sub-field should be ignored
92+
create_fut("city", "STRING", "NULLABLE") # This sub-field should be ignored
93+
])
94+
]),
95+
create_fut("tags", "STRING", "REPEATED"),
96+
],
97+
["id", "user_profile", "user_profile.name", "user_profile.addresses", "tags"],
98+
),
99+
(
100+
"Empty input list of fields",
101+
[],
102+
[],
103+
),
104+
(
105+
"Field type not in STRUCT_FIELD_TYPES and mode is REPEATED",
106+
[
107+
create_fut("f_arr", "FLOAT", "REPEATED")
108+
],
109+
["f_arr"]
110+
),
111+
(
112+
"Field type not in STRUCT_FIELD_TYPES and mode is not REPEATED",
113+
[
114+
create_fut("f_single", "DATE", "NULLABLE")
115+
],
116+
["f_single"]
117+
)
118+
]
119+
120+
121+
@pytest.mark.parametrize(
122+
"description, input_fields_list, expected_field_names",
123+
test_cases
124+
)
125+
def test_get_transitive_schema_fields_conditions(description, input_fields_list, expected_field_names):
126+
"""
127+
Tests the _get_transitive_schema_fields function, focusing on the conditional logic
128+
`if field.field_type in STRUCT_FIELD_TYPES and field.mode != "REPEATED":`.
129+
"""
130+
result_fields = _get_transitive_schema_fields(input_fields_list)
131+
result_names = [field.name for field in result_fields]
132+
assert result_names == expected_field_names, description

0 commit comments

Comments
 (0)