diff --git a/CHANGELOG.md b/CHANGELOG.md index 8495dff3..43637720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ Older versions of this project were distributed as [pybigquery][0]. [2]: https://pypi.org/project/pybigquery/#history +### [1.2.2](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.1...v1.2.2) (2021-10-29) + + +### Bug Fixes + +* avoid aliasing known tables used in CTEs ([#369](https://www.github.com/googleapis/python-bigquery-sqlalchemy/issues/369)) ([4b05d21](https://www.github.com/googleapis/python-bigquery-sqlalchemy/commit/4b05d21b8dc89339a69df87183f8893bf02459c5)) + ### [1.2.1](https://www.github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.2.0...v1.2.1) (2021-10-27) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 94f7ef6a..ade1580c 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,11 +1,11 @@ attrs==21.2.0 -google-cloud-testutils==1.1.0 +google-cloud-testutils==1.2.0 importlib-metadata==4.8.1 iniconfig==1.1.1 packaging==21.0 pluggy==1.0.0 py==1.10.0 -pyparsing==2.4.7 +pyparsing==3.0.2 pytest==6.2.5 toml==0.10.2 typing-extensions==3.10.0.2 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 5e266cdf..69731ae7 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -2,7 +2,7 @@ aiocontextvars==0.2.2 attrs==21.2.0 cachetools==4.2.4 certifi==2021.10.8 -cffi==1.14.6 +cffi==1.15.0 charset-normalizer==2.0.7 click==8.0.3 click-plugins==1.1.1 @@ -15,16 +15,16 @@ future==0.18.2 GeoAlchemy2==0.9.4 geopandas==0.9.0; python_version < '3.7' geopandas==0.10.0; python_version >= '3.7' -google-api-core==2.1.0 -google-auth==2.3.0 +google-api-core==2.2.0 +google-auth==2.3.2 google-cloud-bigquery==2.28.1 google-cloud-bigquery-storage==2.9.1 google-cloud-core==2.1.0 google-crc32c==1.3.0 -google-resumable-media==2.0.3 +google-resumable-media==2.1.0 googleapis-common-protos==1.53.0 greenlet==1.1.2 -grpcio==1.41.0 +grpcio==1.41.1 idna==3.3 immutables==0.16 importlib-metadata==4.8.1 @@ -33,31 +33,31 @@ munch==2.5.0 mypy-extensions==0.4.3 numpy==1.19.5; python_version < '3.7' numpy==1.21.2; python_version >= '3.7' -opentelemetry-api==1.5.0 -opentelemetry-instrumentation==0.24b0 -opentelemetry-sdk==1.5.0 -opentelemetry-semantic-conventions==0.24b0 +opentelemetry-api==1.6.2 +opentelemetry-instrumentation==0.25b2 +opentelemetry-sdk==1.6.2 +opentelemetry-semantic-conventions==0.25b2 packaging==21.0 pandas==1.1.5; python_version < '3.7' pandas==1.3.2; python_version >= '3.7' -proto-plus==1.19.5 -protobuf==3.18.1 -pyarrow==5.0.0 +proto-plus==1.19.7 +protobuf==3.19.0 +pyarrow==6.0.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.20 -pyparsing==2.4.7 +pyparsing==3.0.2 pyproj==3.0.1; python_version < '3.7' pyproj==3.1.0; python_version >= '3.7' python-dateutil==2.8.2 pytz==2021.3 -PyYAML==5.4.1 +PyYAML==6.0 requests==2.26.0 rsa==4.7.2 -Shapely==1.7.1 +Shapely==1.8.0 six==1.16.0 -SQLAlchemy==1.4.25 -sqlalchemy-bigquery==1.2.0 +SQLAlchemy==1.4.26 +sqlalchemy-bigquery==1.2.1 tqdm==4.62.3 typing-extensions==3.10.0.2 typing-inspect==0.7.1 diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index f2da562d..ae96d6f4 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -51,6 +51,7 @@ from sqlalchemy.engine.base import Engine from sqlalchemy.sql.schema import Column from sqlalchemy.sql.schema import Table +from sqlalchemy.sql.selectable import CTE from sqlalchemy.sql import elements, selectable import re @@ -254,6 +255,20 @@ def visit_table_valued_alias(self, element, **kw): ret = f"{aliases}, {ret}" return ret + def _known_tables(self): + known_tables = set() + + for from_ in self.compile_state.froms: + if isinstance(from_, Table): + known_tables.add(from_.name) + elif isinstance(from_, CTE): + for column in from_.original.selected_columns: + table = getattr(column, "table", None) + if table is not None: + known_tables.add(table.name) + + return known_tables + def visit_column( self, column, @@ -290,12 +305,7 @@ def visit_column( if isinstance(tablename, elements._truncated_label): tablename = self._truncated_identifier("alias", tablename) elif TABLE_VALUED_ALIAS_ALIASES in kwargs: - known_tables = set( - from_.name - for from_ in self.compile_state.froms - if isinstance(from_, Table) - ) - if tablename not in known_tables: + if tablename not in self._known_tables(): aliases = kwargs[TABLE_VALUED_ALIAS_ALIASES] if tablename not in aliases: aliases[tablename] = self.anon_map[ diff --git a/sqlalchemy_bigquery/version.py b/sqlalchemy_bigquery/version.py index 66fe5995..24e37be5 100644 --- a/sqlalchemy_bigquery/version.py +++ b/sqlalchemy_bigquery/version.py @@ -17,4 +17,4 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__version__ = "1.2.1" +__version__ = "1.2.2" diff --git a/tests/unit/test_compiler.py b/tests/unit/test_compiler.py index 5da4e935..889ad63d 100644 --- a/tests/unit/test_compiler.py +++ b/tests/unit/test_compiler.py @@ -76,3 +76,39 @@ def test_no_alias_for_known_tables(faux_conn, metadata): ) found_sql = q.compile(faux_conn).string assert found_sql == expected_sql + + +@sqlalchemy_1_4_or_higher +def test_no_alias_for_known_tables_cte(faux_conn, metadata): + # See: https://github.com/googleapis/python-bigquery-sqlalchemy/issues/368 + table = setup_table( + faux_conn, + "table1", + metadata, + sqlalchemy.Column("foo", sqlalchemy.Integer), + sqlalchemy.Column("bars", sqlalchemy.ARRAY(sqlalchemy.Integer)), + ) + F = sqlalchemy.func + + # Set up initiali query + q = sqlalchemy.select(table.c.foo, F.unnest(table.c.bars).column_valued("bar")) + + expected_initial_sql = ( + "SELECT `table1`.`foo`, `bar` \n" + "FROM `table1`, unnest(`table1`.`bars`) AS `bar`" + ) + found_initial_sql = q.compile(faux_conn).string + assert found_initial_sql == expected_initial_sql + + q = q.cte("cte") + q = sqlalchemy.select(*q.columns) + + expected_cte_sql = ( + "WITH `cte` AS \n" + "(SELECT `table1`.`foo` AS `foo`, `bar` \n" + "FROM `table1`, unnest(`table1`.`bars`) AS `bar`)\n" + " SELECT `cte`.`foo`, `cte`.`bar` \n" + "FROM `cte`" + ) + found_cte_sql = q.compile(faux_conn).string + assert found_cte_sql == expected_cte_sql