Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for SQLAlchemy 1.4 #177

Merged
merged 228 commits into from May 21, 2021
Merged
Show file tree
Hide file tree
Changes from 223 commits
Commits
Show all changes
228 commits
Select commit Hold shift + click to select a range
0fc64fa
Fixed a dependency problem that caysed test failures in Python 3.6.
jimfulton Apr 12, 2021
bfdda68
Started implementing SqlAlchemy dialect-compliance tests
jimfulton Apr 15, 2021
41cb2dd
Handle parameters in in
jimfulton Apr 15, 2021
631c060
fixed regex to allow multi-character parameter names.
jimfulton Apr 15, 2021
aa85cb9
Fixed rendering of string literals.
jimfulton Apr 16, 2021
c7da257
Provide default values for primary keys and other fixes...
jimfulton Apr 16, 2021
3916239
Narrowed skips and license comment diagnosed reasons for skipped fail…
jimfulton Apr 16, 2021
46b30b4
use Google license
jimfulton Apr 16, 2021
36a0033
Fixed like (% and _) escapes.
jimfulton Apr 19, 2021
d079fbe
Handle BIGNUMERIC
jimfulton Apr 19, 2021
7e60691
Skip tests that want to stpre floats as numeric.
jimfulton Apr 19, 2021
4a1bafd
skip tests for offsets without limits. BigQuery doesn't allow that.
jimfulton Apr 19, 2021
e07b1f0
BIGNUMERIC lets us handle many significant digits.test_many_significa…
jimfulton Apr 19, 2021
656af06
Skip LongNameBlowoutTest because it requires features (indexes, prima…
jimfulton Apr 19, 2021
a085db6
We have to rewrite these tests, because of forcing use_labels=True
jimfulton Apr 19, 2021
681ea47
BigQuery returns deleted and updated rows, which SQLAlchemy doesn't e…
jimfulton Apr 19, 2021
cde5bf8
Fixed a dependency problem that caysed test failures in Python 3.6.
jimfulton Apr 12, 2021
76cf078
Provide a bigquery mock based on sqlite
jimfulton Apr 20, 2021
ee46a1e
Don't force labels in select.
jimfulton Apr 20, 2021
62432a3
merge riversnake-fix-78
jimfulton Apr 20, 2021
a54b946
No need to work around forced labels anymore.
jimfulton Apr 22, 2021
1320f32
Updated sqlite-bases mock
jimfulton Apr 22, 2021
4f098fd
Added test that types are injected into parameter placeholders.
jimfulton Apr 22, 2021
2c76342
Oops, need to call super even when there are where clauses.
jimfulton Apr 22, 2021
68f9043
When SQLAlchemy thinks it knows a parameter type, include it in the p…
jimfulton Apr 23, 2021
be1e215
Don't skip some numeric tests and skip some CTE tests
jimfulton Apr 23, 2021
39f1955
Can't coerce datetime to date.
jimfulton Apr 23, 2021
9e266f2
reenable some tests now that we pass type infor to bigquery
jimfulton Apr 23, 2021
fa84c95
Enable error on warnings -- a test depends on it.
jimfulton Apr 24, 2021
b01d2ce
Added table and column comment support.
jimfulton Apr 24, 2021
dc91449
get_schema_names should return all of the schema names.
jimfulton Apr 24, 2021
3743abe
Retrieval of view definitions.
jimfulton Apr 24, 2021
dcc7e45
handle dateish literals.
jimfulton Apr 24, 2021
163d031
BigQuery doesn't want schema names in column names.
jimfulton Apr 24, 2021
c47a83d
Allow mutating table descriptions/comments.
jimfulton Apr 24, 2021
9444c20
skip tests that depend on BigQuery keeping track of column details.
jimfulton Apr 24, 2021
30c8ed7
Handle TIMESTAMP literals.
jimfulton Apr 24, 2021
0ef4909
Fixed some broken TIMESTAMP tests.
jimfulton Apr 24, 2021
99629cf
enabled more tests.
jimfulton Apr 24, 2021
5e6e48d
remove extra lines.
jimfulton Apr 26, 2021
83feb1e
blacken
jimfulton Apr 26, 2021
896a082
added missing test for api.py
jimfulton Apr 26, 2021
497cc90
Ignore flake8 complaint about * import.
jimfulton Apr 26, 2021
7e5399a
lint and minor cleanup
jimfulton Apr 26, 2021
e48dd58
Removed unused variable.
jimfulton Apr 26, 2021
395a2d4
Get to 100% coverage in test_parse_url
jimfulton Apr 26, 2021
07af7ef
Don't need because we can't test temp tables
jimfulton Apr 27, 2021
ea99108
requirements.py -- It's used by compliance testing.
jimfulton Apr 27, 2021
7039f05
Added Binary literal handling, and ...
jimfulton Apr 27, 2021
0f2d315
Updated fauxdb to correct for some sqlite differences.
jimfulton Apr 27, 2021
9db1e3b
Fixed santy check
jimfulton Apr 27, 2021
50e3448
blackened
jimfulton Apr 27, 2021
f449c5e
Added a test that exercises type handling for all the types.
jimfulton Apr 27, 2021
d000075
Added table reference tests
jimfulton Apr 28, 2021
190136e
test get_view_definition
jimfulton Apr 28, 2021
acdd4d2
removed a private function that's never called.
jimfulton Apr 28, 2021
3e73a0a
table and view list tests
jimfulton Apr 28, 2021
4c6d72d
Moar unit tests and coverage
jimfulton Apr 29, 2021
349269f
use latest bg release, because we depend on changes there.
jimfulton Apr 29, 2021
717dd77
removed breakpoint that shouldn't have been checked in.
jimfulton Apr 29, 2021
7ba7813
Bypass google authentication.
jimfulton Apr 30, 2021
c22565e
Test JSON deserialization.
jimfulton Apr 30, 2021
dc246ae
Test getting project id from authentication.
jimfulton Apr 30, 2021
b914ee3
removed some unneeded code.
jimfulton Apr 30, 2021
a7e49e9
Simplify string and binary literal processors to not expect None.
jimfulton Apr 30, 2021
53883d5
Tested comment support
jimfulton Apr 30, 2021
5b1e77e
Cleaned up comment handling.
jimfulton Apr 30, 2021
beaf0b6
generalized options handling.
jimfulton Apr 30, 2021
8236d5b
test dialect-options handling.
jimfulton Apr 30, 2021
8e41b4d
constraints are ignored.
jimfulton Apr 30, 2021
e9c5175
Better binary-literal hack
jimfulton May 3, 2021
7835d2c
added tests for BIGNUMERIC.
jimfulton May 3, 2021
dda37eb
Major rework using pickle to overcome type differences.
jimfulton May 3, 2021
b774f79
Added array support
jimfulton May 3, 2021
7a31d12
blacken
jimfulton May 3, 2021
78be3cf
replaced an unreachable branch with an assert.
jimfulton May 3, 2021
297e532
lint
jimfulton May 3, 2021
0adebe1
Enable generation of random primary keys when running compliance tests.
jimfulton May 3, 2021
21121e9
Run the select a couple of ways and assert result.
jimfulton May 3, 2021
b942a61
Only generate random primary keys when running compliance tests.
jimfulton May 3, 2021
4aeb0db
addd fetchall, needed by a test.
jimfulton May 4, 2021
a8add9e
Port compliance tests
jimfulton May 4, 2021
04aaee4
record when array size is set.
jimfulton May 4, 2021
0e081a0
Added a tiny metadata convenience.
jimfulton May 4, 2021
6fe196e
Added arraysize tests.
jimfulton May 4, 2021
67733fd
exercise labels
jimfulton May 4, 2021
725c5d5
Make sure label names are legal identifiers
jimfulton May 4, 2021
cb56463
test disable quoting
jimfulton May 4, 2021
58c3d55
simplifed a test.
jimfulton May 4, 2021
d8e464a
Test forcing quote (even though quotes are forced anyway)
jimfulton May 4, 2021
fabd4c6
Be more careful about doubled %s.
jimfulton May 4, 2021
5cf4009
ported test for %%s
jimfulton May 4, 2021
33642ff
Added IN tests.
jimfulton May 4, 2021
401ad3b
handle UNNEST
jimfulton May 4, 2021
2313f54
"inlined" some helper attrs
jimfulton May 4, 2021
7e57563
moved setup_table to central location
jimfulton May 4, 2021
534a819
ported a test that deals with bind params with indeterminate types
jimfulton May 4, 2021
32f6b95
leverage setup_table
jimfulton May 4, 2021
c762a9c
added a test based on sqlalchemy.testing.suite.test_select.LikeFuncti…
jimfulton May 4, 2021
492530b
leverage setup_table
jimfulton May 4, 2021
15ba275
Added test for labels in group by
jimfulton May 4, 2021
a65bd1a
Added a test for compiling a single column.
jimfulton May 5, 2021
ab873aa
Trying to compile a nameless column gives a meaningful error.
jimfulton May 5, 2021
b1224c3
Get 100% coverage of fauxdbi
jimfulton May 5, 2021
ba21553
Simplified quote decision.
jimfulton May 5, 2021
445832e
Specify proto 4 when pickling, to get predictable behavior across Pyt…
jimfulton May 5, 2021
2ef50bd
Some tests require sqlalchemy 1.3
jimfulton May 5, 2021
5cd127a
require 100% test coverage.
jimfulton May 5, 2021
ed81f2c
need newer sqlalchemy and google-auth
jimfulton May 5, 2021
28e4f40
need sqlalchemy 1.2 because comments.
jimfulton May 5, 2021
9616813
blacken/lint
jimfulton May 5, 2021
0b1e85e
Added code to cleanup schemas at start to avoid spurious errors.
jimfulton May 5, 2021
00d61f4
added copyright
jimfulton May 5, 2021
c58f5f1
added copyright
jimfulton May 5, 2021
7e826e1
fixed copyright
jimfulton May 5, 2021
b469f3f
Updated the compliance setup
jimfulton May 5, 2021
c021366
added copyright
jimfulton May 5, 2021
5af39be
added copyright
jimfulton May 5, 2021
a2e92a3
added copyright
jimfulton May 5, 2021
e85558b
added copyright
jimfulton May 5, 2021
c683b52
added copyright
jimfulton May 5, 2021
b7bc62b
blacken
jimfulton May 5, 2021
69c1d40
merge master
jimfulton May 5, 2021
62d8df2
Make operational errors more informative.
jimfulton May 5, 2021
b2ac135
Fixed better debugging support.
jimfulton May 6, 2021
e5e9786
lint first
jimfulton May 6, 2021
9aa57c2
vargify unuded arguments to avoid spurious sqlalchemy 1.4 error.
jimfulton May 6, 2021
250332b
support sqlalchemy 1.4
jimfulton May 6, 2021
9963202
fetchall should return a sequence, not an iterable.
jimfulton May 6, 2021
c280c1f
sqlalchemy url parameters are now immutable
jimfulton May 6, 2021
7625e61
Don't use upsert to update comments.
jimfulton May 6, 2021
988a0e9
use the default value of returns_unicode_strings
jimfulton May 6, 2021
410b9ac
Older sqlite don't have true and false.
jimfulton May 6, 2021
0b8e3f0
Ignore test debugging info added to deal with sqlite3 differences.
jimfulton May 6, 2021
fa303d7
blacken
jimfulton May 6, 2021
1589a5e
Deal with differences in the way "expanding" is handled in sqlalchemy…
jimfulton May 6, 2021
bbb0b54
Added skips for before and after sqlalchemy 1.4
jimfulton May 6, 2021
0d2169a
Updated tests for sqlalchemy 1.4
jimfulton May 6, 2021
89036ce
blacken
jimfulton May 6, 2021
899300b
Be much more careful about inserting types in placeholders.
jimfulton May 7, 2021
e9a1378
Consider bind param expanding status
jimfulton May 7, 2021
5ce7507
Added a nutty registration to make sqlalchemy 1.4's dialect-testing m…
jimfulton May 7, 2021
3c4c8c7
Made delete handling (in teardown) robust for different sqlalchemy ve…
jimfulton May 7, 2021
3667869
skipped some more tests
jimfulton May 7, 2021
87f2846
Added minimal requirement for google-api-core to be consistent with s…
jimfulton May 10, 2021
dc28277
Make the google-auth requirement consistent with the Python 3.6 test …
jimfulton May 10, 2021
159fd18
chore(revert): revert preventing normalization (#132)
dandhlee Apr 27, 2021
05528af
Explain why we add where clauses in delete statements that lack them
jimfulton May 10, 2021
2db2dd4
Added a missing _ and explained \d+s in a regex
jimfulton May 10, 2021
8ea13de
Fixed: didn't properly handle expansion of single-item arrays.
jimfulton May 10, 2021
ae03721
Added test for expansion of single-element arrays
jimfulton May 10, 2021
486873b
Avoid having to use an `_` to ignore a re group we don't care about.
jimfulton May 10, 2021
a897d60
Added some comments for the tricky _get_field method.
jimfulton May 10, 2021
6e4d5d6
Commented the pickle-protocol 4 prefixes we're looking for.
jimfulton May 10, 2021
708f5db
explain use of pickle.
jimfulton May 10, 2021
eeaae87
typo
jimfulton May 10, 2021
ce6181f
BigQuery 2.15 wants google-api-core >= 1.23.0
jimfulton May 10, 2021
877106b
blacken
jimfulton May 10, 2021
2a25233
BQ requires limits and offsets to be literals.
jimfulton May 10, 2021
6dbf222
Skip test for features that BQ doesn't handle. Also fix a test.
jimfulton May 10, 2021
db7a04f
Correct meaning of UNION and don't expand when arameter type is unknown.
jimfulton May 10, 2021
ddd3399
don't need to call out the project in the test config.
jimfulton May 10, 2021
e063e3e
Try sleeping between tests to see if we can avoid some weird failures.
jimfulton May 10, 2021
7530acf
some minimal docs on the dialect tests.
jimfulton May 10, 2021
4d99090
Update tests/sqlalchemy_dialect_compliance/README.rst
jimfulton May 11, 2021
6b2a6b6
Update tests/sqlalchemy_dialect_compliance/README.rst
jimfulton May 11, 2021
bc40df2
simplify by getting rid of unnecessary and broken overrriding.
jimfulton May 11, 2021
ec4961c
merge upstream
jimfulton May 11, 2021
16e302d
Try sleeping longer to avoid races between tests.
jimfulton May 11, 2021
6793760
Got RowCountTest passing
jimfulton May 11, 2021
1e11ce2
make sure we wait for the drops at the beginning of the session.
jimfulton May 11, 2021
ff35acf
rerun on table not found
jimfulton May 11, 2021
b4c3c23
Try simpler regex
jimfulton May 11, 2021
18ebe26
Try different error
jimfulton May 11, 2021
fbbd563
don't sleep between tests.
jimfulton May 11, 2021
a103ae8
blacken
jimfulton May 11, 2021
5809a9c
removed unused import
jimfulton May 11, 2021
b882818
Don't enable the unicode ddl tests. They create invalid column names.
jimfulton May 12, 2021
7ee98be
fix more tests
jimfulton May 12, 2021
4728f5e
merge master
jimfulton May 12, 2021
bd9d688
Removed machinery to clean tests schemas
jimfulton May 12, 2021
0fa700f
Added an alternate requirements class that skips schema tests.
jimfulton May 12, 2021
43fe9b8
use --dropfirst to clean test schemas at start of compliance run.
jimfulton May 12, 2021
5eae34c
merge master
jimfulton May 12, 2021
1f1a510
really merge master
jimfulton May 12, 2021
c0b742d
document running test simultaneously
jimfulton May 12, 2021
cabffb1
Merge remote-tracking branch 'origin/master' into riversnake-sqla-14
jimfulton May 12, 2021
fff1811
Merged changes to facilitate simultaneous test runs
jimfulton May 12, 2021
d5251ce
fixed merge misfire.
jimfulton May 12, 2021
0b7e193
blacken
jimfulton May 12, 2021
ff3b4fc
removed unused import
jimfulton May 12, 2021
d3f5b7c
removed repeated function definition.
jimfulton May 12, 2021
de6a514
import cleanup and lint
jimfulton May 12, 2021
a83fc85
In sqlite3 UNION DISTINCT is spelled UNION
jimfulton May 12, 2021
c11a3c3
Use newer BQ that has sane executemany rowcount
jimfulton May 13, 2021
634b824
Added a helper to make it easier to manage regex substitutions
jimfulton May 13, 2021
6a53bcb
update BQ
jimfulton May 13, 2021
fd65861
Updated placeholder handling for 1.4
jimfulton May 13, 2021
dc74972
Changed am expectation for 1.4.
jimfulton May 13, 2021
863ef00
merge master
jimfulton May 13, 2021
d2eefa4
We'll always be passed a string that needs to be compiled.
jimfulton May 13, 2021
00ea3ab
notin needs to wrap its result in ()s
jimfulton May 16, 2021
d47a57b
use functools.wraps
jimfulton May 16, 2021
78c3056
Added a test for a gnarly regex
jimfulton May 16, 2021
8e5cea6
Fixed a regex
jimfulton May 16, 2021
2d42d71
adjused some expectations for `not in` being wrapped in ()s
jimfulton May 16, 2021
7a802ee
moar cases
jimfulton May 16, 2021
80f1ee9
Update expectation: we now encode not-in expressions in ()s.
jimfulton May 16, 2021
7596f6d
We can still run with sqla 1.3
jimfulton May 16, 2021
dc21cbf
use more substitute_re_method
jimfulton May 16, 2021
d17c2f0
leverage substitute_re_method
jimfulton May 16, 2021
ff11a31
replaced some unreached paths with assertions.
jimfulton May 17, 2021
08c781c
ported a compliance test to hit an unreached path.
jimfulton May 17, 2021
e06fac2
different sql is generated for `in` before and after 1.4
jimfulton May 17, 2021
ce87a54
made substitute_re_method more methody
jimfulton May 17, 2021
da3ce60
brought back some code paths needed by sqlalchemy 1.3
jimfulton May 17, 2021
26c16a7
make sure we test 1.3
jimfulton May 17, 2021
6996562
lint/blacken
jimfulton May 17, 2021
55ecde6
Merge branch 'master' into riversnake-sqla-14
jimfulton May 17, 2021
9ae173a
removed some no-longer relevant text
jimfulton May 17, 2021
d0edaea
Merge branch 'master' into riversnake-sqla-14
jimfulton May 18, 2021
2c5d992
Removed breakpoint from code that should never be called. :)
jimfulton May 19, 2021
070814e
run system and compliance tests with both sqla 1.3 and 1.4
jimfulton May 19, 2021
0fc9db6
be more careful about sqlalchemy version comparison
jimfulton May 21, 2021
64206c0
use wraps as a decorator
jimfulton May 21, 2021
41edaa5
say why we're using 2 python versions
jimfulton May 21, 2021
738faa0
blacken
jimfulton May 21, 2021
4d80d23
don't wait for compliance tests to finish to find out we have a lint …
jimfulton May 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion noxfile.py
Expand Up @@ -28,7 +28,7 @@
BLACK_PATHS = ["docs", "pybigquery", "tests", "noxfile.py", "setup.py"]

DEFAULT_PYTHON_VERSION = "3.8"
SYSTEM_TEST_PYTHON_VERSIONS = ["3.9"]
SYSTEM_TEST_PYTHON_VERSIONS = ["3.8", "3.9"]
plamut marked this conversation as resolved.
Show resolved Hide resolved
UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"]

CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
Expand Down
21 changes: 21 additions & 0 deletions pybigquery/_helpers.py
Expand Up @@ -4,6 +4,9 @@
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.

import functools
import re

from google.api_core import client_info
import google.auth
from google.cloud import bigquery
Expand Down Expand Up @@ -58,3 +61,21 @@ def create_bigquery_client(
location=location,
default_query_job_config=default_query_job_config,
)


def substitute_re_method(r, flags=0, repl=None):
if repl is None:
return lambda f: substitute_re_method(r, flags, f)

r = re.compile(r, flags)

if isinstance(repl, str):
return lambda self, s: r.sub(repl, s)

def sub(self, s, *args, **kw):
def repl_(m):
return repl(self, m, *args, **kw)

return r.sub(repl_, s)

return functools.wraps(repl)(sub)
plamut marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion pybigquery/parse_url.py
Expand Up @@ -44,7 +44,7 @@ def parse_boolean(bool_string):


def parse_url(url): # noqa: C901
query = url.query
query = dict(url.query) # need mutable query.

# use_legacy_sql (legacy)
if "use_legacy_sql" in query:
Expand Down
16 changes: 15 additions & 1 deletion pybigquery/requirements.py
Expand Up @@ -154,8 +154,14 @@ def comment_reflection(self):
def unicode_ddl(self):
"""Target driver must support some degree of non-ascii symbol
names.

However:

Must contain only letters (a-z, A-Z), numbers (0-9), or underscores (_)

https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_name_and_column_schema
"""
return supported()
return unsupported()

@property
def datetime_literals(self):
Expand Down Expand Up @@ -219,6 +225,14 @@ def order_by_label_with_expression(self):
"""
return supported()

@property
def sql_expression_limit_offset(self):
"""target database can render LIMIT and/or OFFSET with a complete
SQL expression, such as one that uses the addition operator.
parameter
"""
return unsupported()


class WithSchemas(Requirements):
"""
Expand Down
112 changes: 78 additions & 34 deletions pybigquery/sqlalchemy_bigquery.py
Expand Up @@ -34,6 +34,7 @@
from google.cloud.bigquery.table import TableReference
from google.api_core.exceptions import NotFound

import sqlalchemy
import sqlalchemy.sql.sqltypes
import sqlalchemy.sql.type_api
from sqlalchemy.exc import NoSuchTableError
Expand All @@ -57,6 +58,11 @@
FIELD_ILLEGAL_CHARACTERS = re.compile(r"[^\w]+")


def assert_(cond, message="Assertion failed"): # pragma: NO COVER
if not cond:
raise AssertionError(message)


class BigQueryIdentifierPreparer(IdentifierPreparer):
"""
Set containing everything
Expand Down Expand Up @@ -152,15 +158,25 @@ def get_insert_default(self, column): # pragma: NO COVER
elif isinstance(column.type, String):
return str(uuid.uuid4())

def pre_exec(
self,
in_sub=re.compile(
r" IN UNNEST\(\[ "
r"(%\([^)]+_\d+\)s(?:, %\([^)]+_\d+\)s)*)?" # Placeholders. See below.
r":([A-Z0-9]+)" # Type
r" \]\)"
).sub,
):
__remove_type_from_empty_in = _helpers.substitute_re_method(
r" IN UNNEST\(\[ ("
r"(?:NULL|\(NULL(?:, NULL)+\))\)"
r" (?:AND|OR) \(1 !?= 1"
r")"
r"(?:[:][A-Z0-9]+)?"
r" \]\)",
re.IGNORECASE,
r" IN(\1)",
)

@_helpers.substitute_re_method(
r" IN UNNEST\(\[ "
r"(%\([^)]+_\d+\)s(?:, %\([^)]+_\d+\)s)*)?" # Placeholders. See below.
r":([A-Z0-9]+)" # Type
r" \]\)",
re.IGNORECASE,
)
def __distribute_types_to_expanded_placeholders(self, m):
# If we have an in parameter, it sometimes gets expaned to 0 or more
# parameters and we need to move the type marker to each
# parameter.
Expand All @@ -171,29 +187,29 @@ def pre_exec(
# suffixes refect that when an array parameter is expanded,
# numeric suffixes are added. For example, a placeholder like
# `%(foo)s` gets expaneded to `%(foo_0)s, `%(foo_1)s, ...`.
placeholders, type_ = m.groups()
if placeholders:
placeholders = placeholders.replace(")", f":{type_})")
else:
placeholders = ""
return f" IN UNNEST([ {placeholders} ])"

def repl(m):
placeholders, type_ = m.groups()
if placeholders:
placeholders = placeholders.replace(")", f":{type_})")
else:
placeholders = ""
return f" IN UNNEST([ {placeholders} ])"

self.statement = in_sub(repl, self.statement)
def pre_exec(self):
self.statement = self.__distribute_types_to_expanded_placeholders(
self.__remove_type_from_empty_in(self.statement)
)


class BigQueryCompiler(SQLCompiler):

compound_keywords = SQLCompiler.compound_keywords.copy()
compound_keywords[selectable.CompoundSelect.UNION] = "UNION ALL"
compound_keywords[selectable.CompoundSelect.UNION] = "UNION DISTINCT"
compound_keywords[selectable.CompoundSelect.UNION_ALL] = "UNION ALL"

def __init__(self, dialect, statement, column_keys=None, inline=False, **kwargs):
def __init__(self, dialect, statement, *args, **kwargs):
if isinstance(statement, Column):
kwargs["compile_kwargs"] = util.immutabledict({"include_table": False})
super(BigQueryCompiler, self).__init__(
dialect, statement, column_keys, inline, **kwargs
)
super(BigQueryCompiler, self).__init__(dialect, statement, *args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When i worked on this change, i was a bit worried about the consequences of users passing "inline" as a named parameter.

AFAIR - it was removed from SQLA right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't thinking about users passing inline. :)

Yes, SQLA removed it in 1.4. I want this code to work with earlier versions and since we're only using statement, I'm just passing the later arguments along.


def visit_insert(self, insert_stmt, asfrom=False, **kw):
# The (internal) documentation for `inline` is confusing, but
Expand Down Expand Up @@ -260,24 +276,33 @@ def group_by_clause(self, select, **kw):
# no way to tell sqlalchemy that, so it works harder than
# necessary and makes us do the same.

_in_expanding_bind = re.compile(r" IN \((\[EXPANDING_\w+\](:[A-Z0-9]+)?)\)$")

def _unnestify_in_expanding_bind(self, in_text):
return self._in_expanding_bind.sub(r" IN UNNEST([ \1 ])", in_text)
__in_expanding_bind = _helpers.substitute_re_method(
fr" IN \((\["
fr"{'EXPANDING' if sqlalchemy.__version__ < '1.4' else 'POSTCOMPILE'}"
plamut marked this conversation as resolved.
Show resolved Hide resolved
fr"_[^\]]+\](:[A-Z0-9]+)?)\)$",
re.IGNORECASE,
r" IN UNNEST([ \1 ])",
)

def visit_in_op_binary(self, binary, operator_, **kw):
return self._unnestify_in_expanding_bind(
return self.__in_expanding_bind(
self._generate_generic_binary(binary, " IN ", **kw)
)

def visit_empty_set_expr(self, element_types):
return ""

def visit_notin_op_binary(self, binary, operator, **kw):
return self._unnestify_in_expanding_bind(
self._generate_generic_binary(binary, " NOT IN ", **kw)
def visit_not_in_op_binary(self, binary, operator, **kw):
return (
"("
+ self.__in_expanding_bind(
self._generate_generic_binary(binary, " NOT IN ", **kw)
)
+ ")"
)

visit_notin_op_binary = visit_not_in_op_binary # before 1.4

############################################################################

############################################################################
Expand Down Expand Up @@ -365,8 +390,28 @@ def visit_bindparam(
# Values get arrayified at a lower level.
bq_type = bq_type[6:-1]

assert param != "%s"
return param.replace(")", f":{bq_type})")
assert_(param != "%s", f"Unexpected param: {param}")

if bindparam.expanding:
assert_(self.__expanded_param(param), f"Unexpected param: {param}")
param = param.replace(")", f":{bq_type})")

else:
m = self.__placeholder(param)
if m:
name, type_ = m.groups()
assert_(type_ is None)
param = f"%({name}:{bq_type})s"

return param

__placeholder = re.compile(r"%\(([^\]:]+)(:[^\]:]+)?\)s$").match

__expanded_param = re.compile(
fr"\(\["
fr"{'EXPANDING' if sqlalchemy.__version__ < '1.4' else 'POSTCOMPILE'}"
fr"_[^\]]+\]\)$"
).match


class BigQueryTypeCompiler(GenericTypeCompiler):
Expand Down Expand Up @@ -541,7 +586,6 @@ class BigQueryDialect(DefaultDialect):
supports_unicode_statements = True
supports_unicode_binds = True
supports_native_decimal = True
returns_unicode_strings = True
description_encoding = None
supports_native_boolean = True
supports_simple_order_by_label = True
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Expand Up @@ -65,10 +65,10 @@ def readme():
],
platforms="Posix; MacOS X; Windows",
install_requires=[
"sqlalchemy>=1.2.0,<1.4.0dev",
"google-auth>=1.24.0,<2.0dev", # Work around pip wack.
"google-cloud-bigquery>=2.15.0",
"google-api-core>=1.23.0", # Work-around bug in cloud core deps.
"google-auth>=1.24.0,<2.0dev", # Work around pip wack.
"google-cloud-bigquery>=2.16.1",
"sqlalchemy>=1.2.0,<1.5.0dev",
"future",
],
python_requires=">=3.6, <3.10",
Expand Down
2 changes: 1 addition & 1 deletion testing/constraints-3.6.txt
Expand Up @@ -6,5 +6,5 @@
# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
sqlalchemy==1.2.0
google-auth==1.24.0
google-cloud-bigquery==2.15.0
google-cloud-bigquery==2.16.1
google-api-core==1.23.0
1 change: 1 addition & 0 deletions testing/constraints-3.8.txt
@@ -0,0 +1 @@
sqlalchemy==1.3.24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Is that different than 3.9 constraint on SQLA?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure we run with 1.3 in some of the unit tests.

There are code paths needed for 1.3 that aren't exercised with 1.4. Without this, we wouldn't have the required 100% test coverage (and wouldn't be testing with 1.3).

1 change: 1 addition & 0 deletions testing/constraints-3.9.txt
@@ -0,0 +1 @@
sqlalchemy>=1.4.13
5 changes: 5 additions & 0 deletions tests/conftest.py
Expand Up @@ -20,3 +20,8 @@
from sqlalchemy.dialects import registry

registry.register("bigquery", "pybigquery.sqlalchemy_bigquery", "BigQueryDialect")

# sqlalchemy's dialect-testing machinery wants an entry like this. It is wack. :(
registry.register(
"bigquery.bigquery", "pybigquery.sqlalchemy_bigquery", "BigQueryDialect"
)
19 changes: 12 additions & 7 deletions tests/sqlalchemy_dialect_compliance/conftest.py
Expand Up @@ -19,9 +19,9 @@

import contextlib
import random
import re
import traceback

import sqlalchemy
from sqlalchemy.testing import config
from sqlalchemy.testing.plugin.pytestplugin import * # noqa
from sqlalchemy.testing.plugin.pytestplugin import (
Expand All @@ -35,23 +35,28 @@
pybigquery.sqlalchemy_bigquery.BigQueryDialect.preexecute_autoincrement_sequences = True
google.cloud.bigquery.dbapi.connection.Connection.rollback = lambda self: None

_where = re.compile(r"\s+WHERE\s+", re.IGNORECASE).search

# BigQuery requires delete statements to have where clauses. Other
# databases don't and sqlalchemy doesn't include where clauses when
# cleaning up test data. So we add one when we see a delete without a
# where clause when tearing down tests. We only do this during tear
# down, by inspecting the stack, because we don't want to hide bugs
# outside of test house-keeping.
def visit_delete(self, delete_stmt, *args, **kw):
if delete_stmt._whereclause is None and "teardown" in set(
f.name for f in traceback.extract_stack()
):
delete_stmt._whereclause = sqlalchemy.true()

return super(pybigquery.sqlalchemy_bigquery.BigQueryCompiler, self).visit_delete(

def visit_delete(self, delete_stmt, *args, **kw):
text = super(pybigquery.sqlalchemy_bigquery.BigQueryCompiler, self).visit_delete(
delete_stmt, *args, **kw
)

if not _where(text) and any(
"teardown" in f.name.lower() for f in traceback.extract_stack()
):
text += " WHERE true"

return text


pybigquery.sqlalchemy_bigquery.BigQueryCompiler.visit_delete = visit_delete

Expand Down