Skip to content
This repository was archived by the owner on Mar 16, 2026. It is now read-only.

Commit b5dfa5e

Browse files
Fix unnest breakage for other dialects
BigQuery's unnest function implementation overrides and breaks this function for other dialects. This change makes the unnest function dialect-specific to BigQuery to avoid conflicts with other dialects.
1 parent 5aac853 commit b5dfa5e

File tree

4 files changed

+36
-22
lines changed

4 files changed

+36
-22
lines changed

setup.cfg

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
[sqla_testing]
18-
requirement_cls=sqlalchemy_bigquery.requirements:Requirements
19-
profile_file=.sqlalchemy_dialect_compliance-profiles.txt
17+
#[sqla_testing]
18+
#requirement_cls=sqlalchemy_bigquery.requirements:Requirements
19+
#profile_file=.sqlalchemy_dialect_compliance-profiles.txt
2020

2121
[tool:pytest]
22-
addopts= --tb native -v -r fxX
22+
#addopts= --tb native -v -r fxX
2323
python_files=tests/*test_*.py

sqlalchemy_bigquery/base.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1377,7 +1377,12 @@ def get_view_definition(self, connection, view_name, schema=None, **kw):
13771377
return view.view_query
13781378

13791379

1380-
class unnest(sqlalchemy.sql.functions.GenericFunction):
1380+
# unnest is a reserved keyword in some dialects.
1381+
# It is defined here to avoid conflicts.
1382+
# https://github.com/googleapis/python-bigquery-sqlalchemy/issues/882
1383+
class _unnest(sqlalchemy.sql.expression.FunctionElement):
1384+
inherit_cache = True
1385+
13811386
def __init__(self, *args, **kwargs):
13821387
expr = kwargs.pop("expr", None)
13831388
if expr is not None:
@@ -1395,9 +1400,18 @@ def __init__(self, *args, **kwargs):
13951400
):
13961401
raise TypeError("The argument to unnest must have an ARRAY type.")
13971402
self.type = arg.type.item_type
1403+
13981404
super().__init__(*args, **kwargs)
13991405

14001406

1407+
@compiles(_unnest, "bigquery")
1408+
def bigquery_unnest(element, compiler, **kw):
1409+
return "UNNEST({})".format(compiler.process(element.clauses, **kw))
1410+
1411+
1412+
sqlalchemy.sql.functions._FunctionGenerator.unnest = _unnest
1413+
1414+
14011415
dialect = BigQueryDialect
14021416

14031417
try:

tests/unit/test_compiler.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def test_no_alias_for_known_tables(faux_conn, metadata):
9393

9494
expected_sql = (
9595
"SELECT `table1`.`foo` \n"
96-
"FROM `table1`, unnest(`table1`.`bar`) AS `anon_1` \n"
96+
"FROM `table1`, UNNEST(`table1`.`bar`) AS `anon_1` \n"
9797
"WHERE `anon_1` = %(param_1:INT64)s"
9898
)
9999
found_sql = q.compile(faux_conn).string
@@ -116,7 +116,7 @@ def test_no_alias_for_known_tables_cte(faux_conn, metadata):
116116

117117
expected_initial_sql = (
118118
"SELECT `table1`.`foo`, `bar` \n"
119-
"FROM `table1`, unnest(`table1`.`bars`) AS `bar`"
119+
"FROM `table1`, UNNEST(`table1`.`bars`) AS `bar`"
120120
)
121121
found_initial_sql = q.compile(faux_conn).string
122122
assert found_initial_sql == expected_initial_sql
@@ -127,7 +127,7 @@ def test_no_alias_for_known_tables_cte(faux_conn, metadata):
127127
expected_cte_sql = (
128128
"WITH `cte` AS \n"
129129
"(SELECT `table1`.`foo` AS `foo`, `bar` \n"
130-
"FROM `table1`, unnest(`table1`.`bars`) AS `bar`)\n"
130+
"FROM `table1`, UNNEST(`table1`.`bars`) AS `bar`)\n"
131131
" SELECT `cte`.`foo`, `cte`.`bar` \n"
132132
"FROM `cte`"
133133
)
@@ -196,7 +196,7 @@ def test_no_implicit_join_asterix_for_inner_unnest_before_2_0(faux_conn, metadat
196196
q = prepare_implicit_join_base_query(faux_conn, metadata, True, False)
197197
expected_initial_sql = (
198198
"SELECT `table1`.`foo`, `table2`.`bar` \n"
199-
"FROM `table2`, unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
199+
"FROM `table2`, UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
200200
)
201201
found_initial_sql = q.compile(faux_conn).string
202202
assert found_initial_sql == expected_initial_sql
@@ -207,7 +207,7 @@ def test_no_implicit_join_asterix_for_inner_unnest_before_2_0(faux_conn, metadat
207207
expected_outer_sql = (
208208
"SELECT * \n"
209209
"FROM (SELECT `table1`.`foo` AS `foo`, `table2`.`bar` AS `bar` \n"
210-
"FROM `table2`, unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
210+
"FROM `table2`, UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
211211
)
212212
found_outer_sql = q.compile(faux_conn).string
213213
assert found_outer_sql == expected_outer_sql
@@ -219,7 +219,7 @@ def test_no_implicit_join_asterix_for_inner_unnest(faux_conn, metadata):
219219
q = prepare_implicit_join_base_query(faux_conn, metadata, True, False)
220220
expected_initial_sql = (
221221
"SELECT `table1`.`foo`, `table2`.`bar` \n"
222-
"FROM unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`"
222+
"FROM UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`"
223223
)
224224
found_initial_sql = q.compile(faux_conn).string
225225
assert found_initial_sql == expected_initial_sql
@@ -230,7 +230,7 @@ def test_no_implicit_join_asterix_for_inner_unnest(faux_conn, metadata):
230230
expected_outer_sql = (
231231
"SELECT * \n"
232232
"FROM (SELECT `table1`.`foo` AS `foo`, `table2`.`bar` AS `bar` \n"
233-
"FROM unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`) AS `anon_1`"
233+
"FROM UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`) AS `anon_1`"
234234
)
235235
found_outer_sql = q.compile(faux_conn).string
236236
assert found_outer_sql == expected_outer_sql
@@ -242,7 +242,7 @@ def test_no_implicit_join_for_inner_unnest_before_2_0(faux_conn, metadata):
242242
q = prepare_implicit_join_base_query(faux_conn, metadata, True, False)
243243
expected_initial_sql = (
244244
"SELECT `table1`.`foo`, `table2`.`bar` \n"
245-
"FROM `table2`, unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
245+
"FROM `table2`, UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
246246
)
247247
found_initial_sql = q.compile(faux_conn).string
248248
assert found_initial_sql == expected_initial_sql
@@ -253,7 +253,7 @@ def test_no_implicit_join_for_inner_unnest_before_2_0(faux_conn, metadata):
253253
expected_outer_sql = (
254254
"SELECT `anon_1`.`foo` \n"
255255
"FROM (SELECT `table1`.`foo` AS `foo`, `table2`.`bar` AS `bar` \n"
256-
"FROM `table2`, unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
256+
"FROM `table2`, UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
257257
)
258258
found_outer_sql = q.compile(faux_conn).string
259259
assert found_outer_sql == expected_outer_sql
@@ -265,7 +265,7 @@ def test_no_implicit_join_for_inner_unnest(faux_conn, metadata):
265265
q = prepare_implicit_join_base_query(faux_conn, metadata, True, False)
266266
expected_initial_sql = (
267267
"SELECT `table1`.`foo`, `table2`.`bar` \n"
268-
"FROM unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`"
268+
"FROM UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`"
269269
)
270270
found_initial_sql = q.compile(faux_conn).string
271271
assert found_initial_sql == expected_initial_sql
@@ -276,7 +276,7 @@ def test_no_implicit_join_for_inner_unnest(faux_conn, metadata):
276276
expected_outer_sql = (
277277
"SELECT `anon_1`.`foo` \n"
278278
"FROM (SELECT `table1`.`foo` AS `foo`, `table2`.`bar` AS `bar` \n"
279-
"FROM unnest(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`) AS `anon_1`"
279+
"FROM UNNEST(`table2`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`, `table2`) AS `anon_1`"
280280
)
281281
found_outer_sql = q.compile(faux_conn).string
282282
assert found_outer_sql == expected_outer_sql
@@ -289,7 +289,7 @@ def test_no_implicit_join_asterix_for_inner_unnest_no_table2_column(
289289
q = prepare_implicit_join_base_query(faux_conn, metadata, False, False)
290290
expected_initial_sql = (
291291
"SELECT `table1`.`foo` \n"
292-
"FROM `table2` `table2_1`, unnest(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
292+
"FROM `table2` `table2_1`, UNNEST(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
293293
)
294294
found_initial_sql = q.compile(faux_conn).string
295295
assert found_initial_sql == expected_initial_sql
@@ -300,7 +300,7 @@ def test_no_implicit_join_asterix_for_inner_unnest_no_table2_column(
300300
expected_outer_sql = (
301301
"SELECT * \n"
302302
"FROM (SELECT `table1`.`foo` AS `foo` \n"
303-
"FROM `table2` `table2_1`, unnest(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
303+
"FROM `table2` `table2_1`, UNNEST(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
304304
)
305305
found_outer_sql = q.compile(faux_conn).string
306306
assert found_outer_sql == expected_outer_sql
@@ -311,7 +311,7 @@ def test_no_implicit_join_for_inner_unnest_no_table2_column(faux_conn, metadata)
311311
q = prepare_implicit_join_base_query(faux_conn, metadata, False, False)
312312
expected_initial_sql = (
313313
"SELECT `table1`.`foo` \n"
314-
"FROM `table2` `table2_1`, unnest(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
314+
"FROM `table2` `table2_1`, UNNEST(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`"
315315
)
316316
found_initial_sql = q.compile(faux_conn).string
317317
assert found_initial_sql == expected_initial_sql
@@ -322,7 +322,7 @@ def test_no_implicit_join_for_inner_unnest_no_table2_column(faux_conn, metadata)
322322
expected_outer_sql = (
323323
"SELECT `anon_1`.`foo` \n"
324324
"FROM (SELECT `table1`.`foo` AS `foo` \n"
325-
"FROM `table2` `table2_1`, unnest(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
325+
"FROM `table2` `table2_1`, UNNEST(`table2_1`.`foos`) AS `unnested_foos` JOIN `table1` ON `table1`.`foo` = `unnested_foos`) AS `anon_1`"
326326
)
327327
found_outer_sql = q.compile(faux_conn).string
328328
assert found_outer_sql == expected_outer_sql

tests/unit/test_select.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ def test_unnest(faux_conn, alias):
419419
query = fcall.column_valued("foo_objects")
420420
compiled = str(sqlalchemy.select(query).compile(faux_conn.engine))
421421
assert " ".join(compiled.strip().split()) == (
422-
"SELECT `foo_objects` FROM `t` `t_1`, unnest(`t_1`.`objects`) AS `foo_objects`"
422+
"SELECT `foo_objects` FROM `t` `t_1`, UNNEST(`t_1`.`objects`) AS `foo_objects`"
423423
)
424424

425425

@@ -450,7 +450,7 @@ def test_unnest_w_no_table_references(faux_conn, alias):
450450
query = fcall.column_valued()
451451
compiled = str(sqlalchemy.select(query).compile(faux_conn.engine))
452452
assert " ".join(compiled.strip().split()) == (
453-
"SELECT `anon_1` FROM unnest(%(unnest_1)s) AS `anon_1`"
453+
"SELECT `anon_1` FROM UNNEST(%(unnest_1)s) AS `anon_1`"
454454
)
455455

456456

0 commit comments

Comments
 (0)