From 1fc3d74765c53f1bfb8963285d8b5ff2cdaa02ae Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 19 Apr 2024 20:34:40 +0200 Subject: [PATCH 01/17] chore(deps): update all dependencies (#1068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- samples/snippets/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index c3827910..d20a4010 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -4,7 +4,7 @@ charset-normalizer==3.3.2 geoalchemy2==0.14.7 google-api-core[grpc]==2.18.0 google-auth==2.29.0 -google-cloud-bigquery==3.20.1 +google-cloud-bigquery==3.21.0 google-cloud-core==2.4.1 google-crc32c==1.5.0 google-resumable-media==2.7.0 @@ -26,7 +26,7 @@ python-dateutil==2.9.0.post0 pytz==2024.1 requests==2.31.0 rsa==4.9 -shapely==2.0.3 +shapely==2.0.4 six==1.16.0 sqlalchemy===1.4.27 typing-extensions==4.11.0 From ad69c630833bce207784dfbea8eb3c58f316e511 Mon Sep 17 00:00:00 2001 From: Brett Naul Date: Mon, 13 May 2024 16:04:43 -0400 Subject: [PATCH 02/17] fix: Fix partitioning by DATE column (#1074) Fixes #1056. Co-authored-by: Chalmer Lowe --- sqlalchemy_bigquery/base.py | 5 +++++ tests/system/test_sqlalchemy_bigquery.py | 5 +++-- tests/unit/test_table_options.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index e80f2891..636ef9c0 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -831,6 +831,11 @@ def _process_time_partitioning( if time_partitioning.field is not None: field = time_partitioning.field if isinstance( + table.columns[time_partitioning.field].type, + sqlalchemy.sql.sqltypes.DATE, + ): + return f"PARTITION BY {field}" + elif isinstance( table.columns[time_partitioning.field].type, sqlalchemy.sql.sqltypes.TIMESTAMP, ): diff --git a/tests/system/test_sqlalchemy_bigquery.py b/tests/system/test_sqlalchemy_bigquery.py index 457a8ea8..7ea4ccc6 100644 --- a/tests/system/test_sqlalchemy_bigquery.py +++ b/tests/system/test_sqlalchemy_bigquery.py @@ -561,7 +561,8 @@ def test_dml(engine, session, table_dml): assert len(result) == 0 -def test_create_table(engine, bigquery_dataset): +@pytest.mark.parametrize("time_partitioning_field", ["timestamp_c", "date_c"]) +def test_create_table(engine, bigquery_dataset, time_partitioning_field): meta = MetaData() Table( f"{bigquery_dataset}.test_table_create", @@ -581,7 +582,7 @@ def test_create_table(engine, bigquery_dataset): bigquery_friendly_name="test table name", bigquery_expiration_timestamp=datetime.datetime(2183, 3, 26, 8, 30, 0), bigquery_time_partitioning=TimePartitioning( - field="timestamp_c", + field=time_partitioning_field, expiration_ms=1000 * 60 * 60 * 24 * 30, # 30 days ), bigquery_require_partition_filter=True, diff --git a/tests/unit/test_table_options.py b/tests/unit/test_table_options.py index 2147fb1d..2b757e04 100644 --- a/tests/unit/test_table_options.py +++ b/tests/unit/test_table_options.py @@ -193,6 +193,24 @@ def test_table_time_partitioning_with_timestamp_dialect_option(faux_conn): ) +def test_table_time_partitioning_with_date_dialect_option(faux_conn): + # expect table creation to fail as SQLite does not support partitioned tables + with pytest.raises(sqlite3.OperationalError): + setup_table( + faux_conn, + "some_table_2", + sqlalchemy.Column("id", sqlalchemy.Integer), + sqlalchemy.Column("createdAt", sqlalchemy.DATE), + bigquery_time_partitioning=TimePartitioning(field="createdAt"), + ) + + # confirm that the following code creates the correct SQL string + assert " ".join(faux_conn.test_data["execute"][-1][0].strip().split()) == ( + "CREATE TABLE `some_table_2` ( `id` INT64, `createdAt` DATE )" + " PARTITION BY createdAt" + ) + + def test_table_time_partitioning_dialect_option_partition_expiration_days(faux_conn): # expect table creation to fail as SQLite does not support partitioned tables with pytest.raises(sqlite3.OperationalError): From 34e36787e13deda6ea28fdc9ed3effd47093d846 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 21 May 2024 19:24:27 +0200 Subject: [PATCH 03/17] chore(deps): update dependency requests to v2.32.0 [security] (#1078) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index d20a4010..308e5009 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -24,7 +24,7 @@ pyasn1-modules==0.4.0 pyparsing==3.1.2 python-dateutil==2.9.0.post0 pytz==2024.1 -requests==2.31.0 +requests==2.32.0 rsa==4.9 shapely==2.0.4 six==1.16.0 From 5433d58a880ca78ef7ca746103284a3362d73534 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 21 May 2024 23:27:54 +0200 Subject: [PATCH 04/17] chore(deps): update all dependencies (#1069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: Lingqing Gan --- samples/snippets/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 308e5009..e4f50446 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -10,8 +10,8 @@ google-crc32c==1.5.0 google-resumable-media==2.7.0 googleapis-common-protos==1.63.0 greenlet==3.0.3 -grpcio==1.62.1 -grpcio-status==1.62.1 +grpcio==1.62.2 +grpcio-status==1.62.2 idna==3.7 importlib-resources==6.4.0; python_version >= '3.8' mako==1.3.3 From 5d47132f8a504afaf83b0c14eaa165c7126517e0 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 30 May 2024 12:59:22 +0200 Subject: [PATCH 05/17] chore(deps): update all dependencies (#1079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * undo grpcio upgrade grpcio 1.64.0 requires protobuf>=5.26.1,<6.0dev (which introduced a breaking change). Core library doesn't support the new protobuf either. I will isolate the change here and merge the other upgrades first. --------- Co-authored-by: Owl Bot Co-authored-by: Lingqing Gan --- samples/snippets/requirements-test.txt | 2 +- samples/snippets/requirements.txt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 75e34405..0fde71b8 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -4,7 +4,7 @@ google-auth==2.29.0 google-cloud-testutils==1.4.0 iniconfig==2.0.0 packaging==24.0 -pluggy==1.4.0 +pluggy==1.5.0 py==1.11.0 pyasn1==0.6.0 pyasn1-modules==0.4.0 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index e4f50446..d4ab7acd 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,10 +1,10 @@ alembic==1.13.1 certifi==2024.2.2 charset-normalizer==3.3.2 -geoalchemy2==0.14.7 -google-api-core[grpc]==2.18.0 +geoalchemy2==0.15.1 +google-api-core[grpc]==2.19.0 google-auth==2.29.0 -google-cloud-bigquery==3.21.0 +google-cloud-bigquery==3.23.1 google-cloud-core==2.4.1 google-crc32c==1.5.0 google-resumable-media==2.7.0 @@ -14,7 +14,7 @@ grpcio==1.62.2 grpcio-status==1.62.2 idna==3.7 importlib-resources==6.4.0; python_version >= '3.8' -mako==1.3.3 +mako==1.3.5 markupsafe==2.1.5 packaging==24.0 proto-plus==1.23.0 @@ -24,7 +24,7 @@ pyasn1-modules==0.4.0 pyparsing==3.1.2 python-dateutil==2.9.0.post0 pytz==2024.1 -requests==2.32.0 +requests==2.32.2 rsa==4.9 shapely==2.0.4 six==1.16.0 From 0b3ab54c368fecd148eaa101c7939e10f3cf3b05 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 5 Jun 2024 20:44:46 +0200 Subject: [PATCH 06/17] chore(deps): update all dependencies (#1080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * undo grpcio upgrade --------- Co-authored-by: Owl Bot Co-authored-by: Lingqing Gan --- samples/snippets/requirements-test.txt | 2 +- samples/snippets/requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 0fde71b8..7b191ecb 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -13,4 +13,4 @@ pytest===6.2.5 rsa==4.9 six==1.16.0 toml==0.10.2 -typing-extensions==4.11.0 +typing-extensions==4.12.0 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index d4ab7acd..b18eaac3 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -24,10 +24,10 @@ pyasn1-modules==0.4.0 pyparsing==3.1.2 python-dateutil==2.9.0.post0 pytz==2024.1 -requests==2.32.2 +requests==2.32.3 rsa==4.9 shapely==2.0.4 six==1.16.0 sqlalchemy===1.4.27 -typing-extensions==4.11.0 +typing-extensions==4.12.0 urllib3==2.2.1 From 5d5803896d871b1bce0aa5f98309844c58afa119 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 6 Jun 2024 23:59:13 +0200 Subject: [PATCH 07/17] chore(deps): update all dependencies (#1081) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update all dependencies * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * undo grpcio upgrade --------- Co-authored-by: Owl Bot Co-authored-by: Lingqing Gan --- samples/snippets/requirements-test.txt | 2 +- samples/snippets/requirements.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 7b191ecb..df0ceaeb 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -13,4 +13,4 @@ pytest===6.2.5 rsa==4.9 six==1.16.0 toml==0.10.2 -typing-extensions==4.12.0 +typing-extensions==4.12.1 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index b18eaac3..939151ae 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,14 +1,14 @@ alembic==1.13.1 -certifi==2024.2.2 +certifi==2024.6.2 charset-normalizer==3.3.2 geoalchemy2==0.15.1 google-api-core[grpc]==2.19.0 google-auth==2.29.0 -google-cloud-bigquery==3.23.1 +google-cloud-bigquery==3.24.0 google-cloud-core==2.4.1 google-crc32c==1.5.0 google-resumable-media==2.7.0 -googleapis-common-protos==1.63.0 +googleapis-common-protos==1.63.1 greenlet==3.0.3 grpcio==1.62.2 grpcio-status==1.62.2 @@ -29,5 +29,5 @@ rsa==4.9 shapely==2.0.4 six==1.16.0 sqlalchemy===1.4.27 -typing-extensions==4.12.0 +typing-extensions==4.12.1 urllib3==2.2.1 From d766d21053f7d9df5019d0d6dedf4476ef6125a9 Mon Sep 17 00:00:00 2001 From: Lingqing Gan Date: Thu, 13 Jun 2024 09:53:24 -0700 Subject: [PATCH 08/17] feat: support UPDATE + JOIN in BigQuery dialect (#1083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support UPDATE JOIN * vendor code from sqlalchemy * remove code and add comments * add vendored folder to path in MANIFEST.in * remove extra code * include third_party in pytest * update MANIFEST.in * add init file * add init file * add pyproject.toml * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update noxfile and owlbot * lint * fix owlbot * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * undo changes for testing * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * add comments to pyproject.toml --------- Co-authored-by: Owl Bot --- MANIFEST.in | 3 +- noxfile.py | 9 +++++- owlbot.py | 21 +++++++++++++ pyproject.toml | 5 ++++ setup.py | 17 ++++++++++- sqlalchemy_bigquery/base.py | 3 +- tests/unit/test_compiler.py | 29 ++++++++++++++++++ third_party/__init__.py | 0 .../sqlalchemy_bigquery_vendored/__init__.py | 0 .../sqlalchemy_bigquery_vendored/py.typed | 0 .../sqlalchemy/AUTHORS | 30 +++++++++++++++++++ .../sqlalchemy/LICENSE | 19 ++++++++++++ .../sqlalchemy/__init__.py | 6 ++++ .../sqlalchemy/postgresql/__init__.py | 0 .../sqlalchemy/postgresql/base.py | 19 ++++++++++++ 15 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 pyproject.toml create mode 100644 third_party/__init__.py create mode 100644 third_party/sqlalchemy_bigquery_vendored/__init__.py create mode 100644 third_party/sqlalchemy_bigquery_vendored/py.typed create mode 100644 third_party/sqlalchemy_bigquery_vendored/sqlalchemy/AUTHORS create mode 100644 third_party/sqlalchemy_bigquery_vendored/sqlalchemy/LICENSE create mode 100644 third_party/sqlalchemy_bigquery_vendored/sqlalchemy/__init__.py create mode 100644 third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/__init__.py create mode 100644 third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/base.py diff --git a/MANIFEST.in b/MANIFEST.in index e0a66705..66fc8ef3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,7 +16,8 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include google *.json *.proto py.typed +recursive-include third_party/sqlalchemy_bigquery_vendored * +recursive-include sqlalchemy_bigquery *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ diff --git a/noxfile.py b/noxfile.py index 36729727..420b097c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -31,7 +31,14 @@ FLAKE8_VERSION = "flake8==6.1.0" BLACK_VERSION = "black[jupyter]==23.7.0" ISORT_VERSION = "isort==5.11.0" -LINT_PATHS = ["docs", "sqlalchemy_bigquery", "tests", "noxfile.py", "setup.py"] +LINT_PATHS = [ + "third_party", + "docs", + "sqlalchemy_bigquery", + "tests", + "noxfile.py", + "setup.py", +] DEFAULT_PYTHON_VERSION = "3.8" diff --git a/owlbot.py b/owlbot.py index 9d4aaafc..0aaa33bf 100644 --- a/owlbot.py +++ b/owlbot.py @@ -15,6 +15,7 @@ """This script is used to synthesize generated parts of this library.""" import pathlib +import re import synthtool as s from synthtool import gcp @@ -76,6 +77,11 @@ "import re\nimport shutil", ) +s.replace( + ["noxfile.py"], + "LINT_PATHS = \[", + "LINT_PATHS = [\"third_party\", " +) s.replace( ["noxfile.py"], @@ -83,6 +89,12 @@ "--cov=sqlalchemy_bigquery", ) +s.replace( + ["noxfile.py"], + """os.path.join("tests", "unit"),""", + """os.path.join("tests", "unit"), + os.path.join("third_party", "sqlalchemy_bigquery_vendored"),""", +) s.replace( ["noxfile.py"], @@ -284,6 +296,15 @@ def system_noextras(session): """, ) + +# Make sure build includes all necessary files. +s.replace( + ["MANIFEST.in"], + re.escape("recursive-include google"), + """recursive-include third_party/sqlalchemy_bigquery_vendored * +recursive-include sqlalchemy_bigquery""", +) + # ---------------------------------------------------------------------------- # Samples templates # ---------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0a21fc9c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +# Added so third_party folder is included when running `pip install -e .` +# See PR #1083 for more detail +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index b33e1c6e..007d001f 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ import itertools import os import re +import setuptools from setuptools import setup # Package metadata. @@ -67,6 +68,16 @@ def readme(): extras["all"] = set(itertools.chain.from_iterable(extras.values())) +packages = [ + package + for package in setuptools.find_namespace_packages() + if package.startswith("sqlalchemy_bigquery") +] + [ + package + for package in setuptools.find_namespace_packages("third_party") + if package.startswith("sqlalchemy_bigquery_vendored") +] + setup( name=name, version=version, @@ -75,7 +86,11 @@ def readme(): long_description_content_type="text/x-rst", author="The Sqlalchemy-Bigquery Authors", author_email="googleapis-packages@google.com", - packages=["sqlalchemy_bigquery"], + package_dir={ + "sqlalchemy-bigquery": "sqlalchemy_bigquery", + "sqlalchemy_bigquery_vendored": "third_party/sqlalchemy_bigquery_vendored", + }, + packages=packages, url="https://github.com/googleapis/python-bigquery-sqlalchemy", keywords=["bigquery", "sqlalchemy"], classifiers=[ diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index 636ef9c0..cffb9daa 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -60,6 +60,7 @@ from .parse_url import parse_url from . import _helpers, _struct, _types +import sqlalchemy_bigquery_vendored.sqlalchemy.postgresql.base as vendored_postgresql # Illegal characters is intended to be all characters that are not explicitly # allowed as part of the flexible column names. @@ -189,7 +190,7 @@ def pre_exec(self): ) -class BigQueryCompiler(_struct.SQLCompiler, SQLCompiler): +class BigQueryCompiler(_struct.SQLCompiler, vendored_postgresql.PGCompiler): compound_keywords = SQLCompiler.compound_keywords.copy() compound_keywords[selectable.CompoundSelect.UNION] = "UNION DISTINCT" compound_keywords[selectable.CompoundSelect.UNION_ALL] = "UNION ALL" diff --git a/tests/unit/test_compiler.py b/tests/unit/test_compiler.py index cc9116e3..60ff3f0a 100644 --- a/tests/unit/test_compiler.py +++ b/tests/unit/test_compiler.py @@ -161,6 +161,35 @@ def prepare_implicit_join_base_query( return q +# Test vendored method update_from_clause() +# from sqlalchemy_bigquery_vendored.sqlalchemy.postgresql.base.PGCompiler +def test_update_from_clause(faux_conn, metadata): + table1 = setup_table( + faux_conn, + "table1", + metadata, + sqlalchemy.Column("foo", sqlalchemy.String), + sqlalchemy.Column("bar", sqlalchemy.Integer), + ) + table2 = setup_table( + faux_conn, + "table2", + metadata, + sqlalchemy.Column("foo", sqlalchemy.String), + sqlalchemy.Column("bar", sqlalchemy.Integer), + ) + + stmt = ( + sqlalchemy.update(table1) + .where(table1.c.foo == table2.c.foo) + .where(table2.c.bar == 1) + .values(bar=2) + ) + expected_sql = "UPDATE `table1` SET `bar`=%(bar:INT64)s FROM `table2` WHERE `table1`.`foo` = `table2`.`foo` AND `table2`.`bar` = %(bar_1:INT64)s" + found_sql = stmt.compile(faux_conn).string + assert found_sql == expected_sql + + @sqlalchemy_before_2_0 def test_no_implicit_join_asterix_for_inner_unnest_before_2_0(faux_conn, metadata): # See: https://github.com/googleapis/python-bigquery-sqlalchemy/issues/368 diff --git a/third_party/__init__.py b/third_party/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/sqlalchemy_bigquery_vendored/__init__.py b/third_party/sqlalchemy_bigquery_vendored/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/sqlalchemy_bigquery_vendored/py.typed b/third_party/sqlalchemy_bigquery_vendored/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/AUTHORS b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/AUTHORS new file mode 100644 index 00000000..98c5e111 --- /dev/null +++ b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/AUTHORS @@ -0,0 +1,30 @@ +SQLAlchemy was created by Michael Bayer. + +Major contributing authors include: + +- Mike Bayer +- Jason Kirtland +- Michael Trier +- Diana Clarke +- Gaetan de Menten +- Lele Gaifax +- Jonathan Ellis +- Gord Thompson +- Federico Caselli +- Philip Jenvey +- Rick Morrison +- Chris Withers +- Ants Aasma +- Sheila Allen +- Paul Johnston +- Tony Locke +- Hajime Nakagami +- Vraj Mohan +- Robert Leftwich +- Taavi Burns +- Jonathan Vanasco +- Jeff Widman +- Scott Dugas +- Dobes Vandermeer +- Ville Skytta +- Rodrigo Menezes diff --git a/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/LICENSE b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/LICENSE new file mode 100644 index 00000000..967cdc5d --- /dev/null +++ b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/LICENSE @@ -0,0 +1,19 @@ +Copyright 2005-2024 SQLAlchemy authors and contributors . + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER 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. diff --git a/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/__init__.py b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/__init__.py new file mode 100644 index 00000000..71aff78e --- /dev/null +++ b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/__init__.py @@ -0,0 +1,6 @@ +# __init__.py +# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php diff --git a/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/__init__.py b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/base.py b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/base.py new file mode 100644 index 00000000..b43ec44f --- /dev/null +++ b/third_party/sqlalchemy_bigquery_vendored/sqlalchemy/postgresql/base.py @@ -0,0 +1,19 @@ +# dialects/postgresql/base.py +# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: ignore-errors + +from sqlalchemy.sql import compiler + + +class PGCompiler(compiler.SQLCompiler): + def update_from_clause( + self, update_stmt, from_table, extra_froms, from_hints, **kw + ): + kw["asfrom"] = True + return "FROM " + ", ".join( + t._compiler_dispatch(self, fromhints=from_hints, **kw) for t in extra_froms + ) From 155e5b013518d702356cec772bee95d770d7c3b0 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 20 Jun 2024 19:18:42 +0200 Subject: [PATCH 09/17] chore(deps): update dependency urllib3 to v2.2.2 [security] (#1086) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 939151ae..9cdf3918 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -30,4 +30,4 @@ shapely==2.0.4 six==1.16.0 sqlalchemy===1.4.27 typing-extensions==4.12.1 -urllib3==2.2.1 +urllib3==2.2.2 From 0577eb77151226d28147cd41a481e16930f197ea Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 9 Jul 2024 01:28:08 +0200 Subject: [PATCH 10/17] chore(deps): update dependency certifi to v2024.7.4 [security] (#1091) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 9cdf3918..e30275cb 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,5 +1,5 @@ alembic==1.13.1 -certifi==2024.6.2 +certifi==2024.7.4 charset-normalizer==3.3.2 geoalchemy2==0.15.1 google-api-core[grpc]==2.19.0 From f5fb1a2543e8196e076d74848a7ae0dcf169f667 Mon Sep 17 00:00:00 2001 From: Alex Holyoke Date: Wed, 10 Jul 2024 09:13:09 -0400 Subject: [PATCH 11/17] fix: Implement modulus operator (#1048) Co-authored-by: Lingqing Gan Co-authored-by: Chalmer Lowe --- sqlalchemy_bigquery/base.py | 3 +++ tests/unit/test_select.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index cffb9daa..38d8f5c1 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -581,6 +581,9 @@ def visit_regexp_match_op_binary(self, binary, operator, **kw): def visit_not_regexp_match_op_binary(self, binary, operator, **kw): return "NOT %s" % self.visit_regexp_match_op_binary(binary, operator, **kw) + def visit_mod_binary(self, binary, operator, **kw): + return f"MOD({self.process(binary.left, **kw)}, {self.process(binary.right, **kw)})" + class BigQueryTypeCompiler(GenericTypeCompiler): def visit_INTEGER(self, type_, **kw): diff --git a/tests/unit/test_select.py b/tests/unit/test_select.py index ad80047a..07f21443 100644 --- a/tests/unit/test_select.py +++ b/tests/unit/test_select.py @@ -406,3 +406,16 @@ def test_visit_not_regexp_match_op_binary(faux_conn): expected = "NOT REGEXP_CONTAINS(`table`.`foo`, %(foo_1:STRING)s)" assert result == expected + + +def test_visit_mod_binary(faux_conn): + table = setup_table( + faux_conn, + "table", + sqlalchemy.Column("foo", sqlalchemy.Integer), + ) + sql_statement = table.c.foo % 2 + result = sql_statement.compile(faux_conn).string + expected = "MOD(`table`.`foo`, %(foo_1:INT64)s)" + + assert result == expected From 9e0b117b6966ad72bc94c0916be95189e4bd9654 Mon Sep 17 00:00:00 2001 From: Alex Holyoke Date: Thu, 18 Jul 2024 14:33:52 -0400 Subject: [PATCH 12/17] fix: Set cte_follows_insert to True (#1095) --- sqlalchemy_bigquery/base.py | 1 + .../sqlalchemy_dialect_compliance/test_dialect_compliance.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index 38d8f5c1..b29ea919 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -990,6 +990,7 @@ class BigQueryDialect(DefaultDialect): type_compiler = BigQueryTypeCompiler ddl_compiler = BigQueryDDLCompiler execution_ctx_cls = BigQueryExecutionContext + cte_follows_insert = True supports_alter = False supports_comments = True inline_comments = True diff --git a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py index 57cd9a0d..4af38a84 100644 --- a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py +++ b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py @@ -537,10 +537,6 @@ def test_round_trip_executemany(self, connection): class CTETest(_CTETest): - @pytest.mark.skip("Can't use CTEs with insert") - def test_insert_from_select_round_trip(self): - pass - @pytest.mark.skip("Recusive CTEs aren't supported.") def test_select_recursive_round_trip(self): pass From 80781ef99287af2e950f21ca399c84d20422b732 Mon Sep 17 00:00:00 2001 From: Alex Holyoke Date: Thu, 18 Jul 2024 17:24:14 -0400 Subject: [PATCH 13/17] fix: Use except distinct and intersect distinct (#1094) Co-authored-by: Lingqing Gan --- sqlalchemy_bigquery/base.py | 2 + tests/unit/test_select.py | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index b29ea919..7398fdac 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -194,6 +194,8 @@ class BigQueryCompiler(_struct.SQLCompiler, vendored_postgresql.PGCompiler): compound_keywords = SQLCompiler.compound_keywords.copy() compound_keywords[selectable.CompoundSelect.UNION] = "UNION DISTINCT" compound_keywords[selectable.CompoundSelect.UNION_ALL] = "UNION ALL" + compound_keywords[selectable.CompoundSelect.EXCEPT] = "EXCEPT DISTINCT" + compound_keywords[selectable.CompoundSelect.INTERSECT] = "INTERSECT DISTINCT" def __init__(self, dialect, statement, *args, **kwargs): if isinstance(statement, Column): diff --git a/tests/unit/test_select.py b/tests/unit/test_select.py index 07f21443..9d2ba21e 100644 --- a/tests/unit/test_select.py +++ b/tests/unit/test_select.py @@ -168,6 +168,94 @@ def test_typed_parameters(faux_conn, type_, val, btype, vrep): ) +def test_except(faux_conn): + table = setup_table( + faux_conn, + "table", + sqlalchemy.Column("id", sqlalchemy.Integer), + sqlalchemy.Column("foo", sqlalchemy.Integer), + ) + + s1 = sqlalchemy.select(table.c.foo).where(table.c.id >= 2) + s2 = sqlalchemy.select(table.c.foo).where(table.c.id >= 4) + + s3 = s1.except_(s2) + + result = s3.compile(faux_conn).string + + expected = ( + "SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_1:INT64)s EXCEPT DISTINCT SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_2:INT64)s" + ) + assert result == expected + + +def test_intersect(faux_conn): + table = setup_table( + faux_conn, + "table", + sqlalchemy.Column("id", sqlalchemy.Integer), + sqlalchemy.Column("foo", sqlalchemy.Integer), + ) + + s1 = sqlalchemy.select(table.c.foo).where(table.c.id >= 2) + s2 = sqlalchemy.select(table.c.foo).where(table.c.id >= 4) + + s3 = s1.intersect(s2) + + result = s3.compile(faux_conn).string + + expected = ( + "SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_1:INT64)s INTERSECT DISTINCT SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_2:INT64)s" + ) + assert result == expected + + +def test_union(faux_conn): + table = setup_table( + faux_conn, + "table", + sqlalchemy.Column("id", sqlalchemy.Integer), + sqlalchemy.Column("foo", sqlalchemy.Integer), + ) + + s1 = sqlalchemy.select(table.c.foo).where(table.c.id >= 2) + s2 = sqlalchemy.select(table.c.foo).where(table.c.id >= 4) + + s3 = s1.union(s2) + + result = s3.compile(faux_conn).string + + expected = ( + "SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_1:INT64)s UNION DISTINCT SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_2:INT64)s" + ) + assert result == expected + + s4 = s1.union_all(s2) + + result = s4.compile(faux_conn).string + + expected = ( + "SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_1:INT64)s UNION ALL SELECT `table`.`foo` \n" + "FROM `table` \n" + "WHERE `table`.`id` >= %(id_2:INT64)s" + ) + assert result == expected + + def test_select_struct(faux_conn, metadata): from sqlalchemy_bigquery import STRUCT From f9324e35a6aa2f3d9c9f2511d1104fdf60c97c83 Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Mon, 5 Aug 2024 07:14:51 -0400 Subject: [PATCH 14/17] feat: adds user agent parameters to two functions (#1100) * adds user agents parameters to two functions * adds typing import * adds test for google_client_info * tweaks test to only check starting characters * updates linting * updates type hints, linting * updates type hints, linting - redux * add system test --------- Co-authored-by: Lingqing Gan --- sqlalchemy_bigquery/_helpers.py | 52 ++++++++++++++++++++++++++------- tests/system/test_helpers.py | 8 +++++ tests/unit/test_helpers.py | 13 +++++++++ 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/sqlalchemy_bigquery/_helpers.py b/sqlalchemy_bigquery/_helpers.py index b03e232a..179ca773 100644 --- a/sqlalchemy_bigquery/_helpers.py +++ b/sqlalchemy_bigquery/_helpers.py @@ -6,6 +6,7 @@ import functools import re +from typing import Optional from google.api_core import client_info import google.auth @@ -24,19 +25,48 @@ ) -def google_client_info(): - user_agent = USER_AGENT_TEMPLATE.format(sqlalchemy.__version__) +def google_client_info( + user_agent: Optional[str] = None, +) -> google.api_core.client_info.ClientInfo: + """ + Return a client_info object, with an optional user agent + string. If user_agent is None, use a default value. + """ + + if user_agent is None: + user_agent = USER_AGENT_TEMPLATE.format(sqlalchemy.__version__) return client_info.ClientInfo(user_agent=user_agent) def create_bigquery_client( - credentials_info=None, - credentials_path=None, - credentials_base64=None, - default_query_job_config=None, - location=None, - project_id=None, -): + credentials_info: Optional[dict] = None, + credentials_path: Optional[str] = None, + credentials_base64: Optional[str] = None, + default_query_job_config: Optional[google.cloud.bigquery.job.QueryJobConfig] = None, + location: Optional[str] = None, + project_id: Optional[str] = None, + user_agent: Optional[google.api_core.client_info.ClientInfo] = None, +) -> google.cloud.bigquery.Client: + """Construct a BigQuery client object. + + Args: + credentials_info Optional[dict]: + credentials_path Optional[str]: + credentials_base64 Optional[str]: + default_query_job_config (Optional[google.cloud.bigquery.job.QueryJobConfig]): + Default ``QueryJobConfig``. + Will be merged into job configs passed into the ``query`` method. + location (Optional[str]): + Default location for jobs / datasets / tables. + project_id (Optional[str]): + Project ID for the project which the client acts on behalf of. + user_agent (Optional[google.api_core.client_info.ClientInfo]): + The client info used to send a user-agent string along with API + requests. If ``None``, then default info will be used. Generally, + you only need to set this if you're developing your own library + or partner tool. + """ + default_project = None if credentials_base64: @@ -60,8 +90,10 @@ def create_bigquery_client( if project_id is None: project_id = default_project + client_info = google_client_info(user_agent=user_agent) + return bigquery.Client( - client_info=google_client_info(), + client_info=client_info, project=project_id, credentials=credentials, location=location, diff --git a/tests/system/test_helpers.py b/tests/system/test_helpers.py index 42cfab7f..222d166c 100644 --- a/tests/system/test_helpers.py +++ b/tests/system/test_helpers.py @@ -104,3 +104,11 @@ def test_create_bigquery_client_with_credentials_base64_respects_project( project_id="connection-url-project", ) assert bqclient.project == "connection-url-project" + + +def test_create_bigquery_client_with_user_agent(module_under_test): + user_agent = "test_user_agent" + + bqclient = module_under_test.create_bigquery_client(user_agent=user_agent) + + assert bqclient._connection._client_info.user_agent == user_agent diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 02bc8bee..e232e4ab 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -12,6 +12,7 @@ import google.auth.credentials import pytest from google.oauth2 import service_account +from sqlalchemy_bigquery import _helpers class AnonymousCredentialsWithProject(google.auth.credentials.AnonymousCredentials): @@ -244,3 +245,15 @@ def foo_to_bar(self, m): Replacer("hah").foo_to_bar("some foo and FOO is good") == "some hah and FOO is good" ) + + +@pytest.mark.parametrize( + "user_agent, expected_user_agent", + [ + (None, f"sqlalchemy/{_helpers.sqlalchemy.__version__}"), + ("my-user-agent", "my-user-agent"), + ], +) +def test_google_client_info(user_agent, expected_user_agent): + client_info = _helpers.google_client_info(user_agent=user_agent) + assert client_info.to_user_agent().startswith(expected_user_agent) From 75569f487cd7dffe833d7c857cd4822b732b723f Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Tue, 13 Aug 2024 15:31:46 -0400 Subject: [PATCH 15/17] Bug: removes a compliance test that fails and replaces with unit test (#1110) * removes a compliance test that fails and replaces with unit test * Update tests/unit/test_select.py * Update tests/unit/test_select.py * Update tests/unit/test_select.py --- .../test_dialect_compliance.py | 4 ++ tests/unit/test_select.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py index 4af38a84..ff14db9a 100644 --- a/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py +++ b/tests/sqlalchemy_dialect_compliance/test_dialect_compliance.py @@ -47,6 +47,7 @@ QuotedNameArgumentTest, SimpleUpdateDeleteTest as _SimpleUpdateDeleteTest, TimestampMicrosecondsTest as _TimestampMicrosecondsTest, + WindowFunctionTest, ) from sqlalchemy.testing.suite.test_types import ( @@ -636,3 +637,6 @@ def test_no_results_for_non_returning_insert(cls): del LongNameBlowoutTest # Requires features (indexes, primary keys, etc., that BigQuery doesn't have. del PostCompileParamsTest # BQ adds backticks to bind parameters, causing failure of tests TODO: fix this? del QuotedNameArgumentTest # Quotes aren't allowed in BigQuery table names. +del ( + WindowFunctionTest.test_window_rows_between +) # test expects BQ to return sorted results diff --git a/tests/unit/test_select.py b/tests/unit/test_select.py index 9d2ba21e..a600bdf9 100644 --- a/tests/unit/test_select.py +++ b/tests/unit/test_select.py @@ -507,3 +507,53 @@ def test_visit_mod_binary(faux_conn): expected = "MOD(`table`.`foo`, %(foo_1:INT64)s)" assert result == expected + + +def test_window_rows_between(faux_conn): + """This is a replacement for the + 'test_window_rows_between' + test in sqlalchemy's suite of compliance tests. + + Their test is expecting things in sorted order and BQ + doesn't return sorted results the way they expect so that + test fails. + + Note: that test only appears in: + sqlalchemy/lib/sqlalchemy/testing/suite/test_select.py + in version 2.0.32. It appears as though that test will be + replaced with a similar but new test called: + 'test_window_rows_between_w_caching' + due to the fact the rows are part of the cache key right now and + not handled as binds. This is related to sqlalchemy Issue #11515 + + It is expected the new test will also have the same sorting failure. + """ + + table = setup_table( + faux_conn, + "table", + sqlalchemy.Column("id", sqlalchemy.String), + sqlalchemy.Column("col1", sqlalchemy.Integer), + sqlalchemy.Column("col2", sqlalchemy.Integer), + ) + + stmt = sqlalchemy.select( + sqlalchemy.func.max(table.c.col2).over( + order_by=[table.c.col1], + rows=(-5, 0), + ) + ) + + sql = stmt.compile( + dialect=faux_conn.dialect, + compile_kwargs={"literal_binds": True}, + ) + + result = str(sql) + expected = ( + "SELECT max(`table`.`col2`) " + "OVER (ORDER BY `table`.`col1` " + "ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS `anon_1` \n" # newline character required here to match + "FROM `table`" + ) + assert result == expected From b54bdde0a01cabf5844c2b2794994b1ae5f4952f Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Wed, 14 Aug 2024 13:03:53 -0400 Subject: [PATCH 16/17] feat: update colspec to account for sqlalchemy Enum (#1111) --- sqlalchemy_bigquery/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sqlalchemy_bigquery/base.py b/sqlalchemy_bigquery/base.py index 7398fdac..c531c102 100644 --- a/sqlalchemy_bigquery/base.py +++ b/sqlalchemy_bigquery/base.py @@ -1018,6 +1018,7 @@ class BigQueryDialect(DefaultDialect): sqlalchemy.sql.sqltypes.Time: BQClassTaggedStr, sqlalchemy.sql.sqltypes.TIMESTAMP: BQTimestamp, sqlalchemy.sql.sqltypes.ARRAY: BQArray, + sqlalchemy.sql.sqltypes.Enum: sqlalchemy.sql.sqltypes.Enum, } def __init__( From d2568da05d0f79a06987a762d48c89a2bbdd402b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:49:25 -0700 Subject: [PATCH 17/17] chore(main): release 1.12.0 (#1075) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 17 +++++++++++++++++ sqlalchemy_bigquery/version.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8619b52..d97c9412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,23 @@ Older versions of this project were distributed as [pybigquery][0]. [2]: https://pypi.org/project/pybigquery/#history +## [1.12.0](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.11.0...v1.12.0) (2024-08-14) + + +### Features + +* Adds user agent parameters to two functions ([#1100](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1100)) ([f9324e3](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/f9324e35a6aa2f3d9c9f2511d1104fdf60c97c83)) +* Support UPDATE + JOIN in BigQuery dialect ([#1083](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1083)) ([d766d21](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/d766d21053f7d9df5019d0d6dedf4476ef6125a9)) +* Update colspec to account for sqlalchemy Enum ([#1111](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1111)) ([b54bdde](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/b54bdde0a01cabf5844c2b2794994b1ae5f4952f)) + + +### Bug Fixes + +* Fix partitioning by DATE column ([#1074](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1074)) ([ad69c63](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/ad69c630833bce207784dfbea8eb3c58f316e511)) +* Implement modulus operator ([#1048](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1048)) ([f5fb1a2](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/f5fb1a2543e8196e076d74848a7ae0dcf169f667)) +* Set cte_follows_insert to True ([#1095](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1095)) ([9e0b117](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/9e0b117b6966ad72bc94c0916be95189e4bd9654)) +* Use except distinct and intersect distinct ([#1094](https://github.com/googleapis/python-bigquery-sqlalchemy/issues/1094)) ([80781ef](https://github.com/googleapis/python-bigquery-sqlalchemy/commit/80781ef99287af2e950f21ca399c84d20422b732)) + ## [1.11.0](https://github.com/googleapis/python-bigquery-sqlalchemy/compare/v1.10.0...v1.11.0) (2024-04-12) diff --git a/sqlalchemy_bigquery/version.py b/sqlalchemy_bigquery/version.py index 6f283d8e..0920d6ea 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.11.0" +__version__ = "1.12.0"