Skip to content

fix: Select expressions no-longer force use of labels #129

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

Merged
merged 3 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions pybigquery/sqlalchemy_bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,6 @@ def __init__(self, dialect, statement, column_keys=None, inline=False, **kwargs)
dialect, statement, column_keys, inline, **kwargs
)

def visit_select(self, *args, **kwargs):
"""
Use labels for every column.
This ensures that fields won't contain duplicate names
"""

args[0].use_labels = True
return super(BigQueryCompiler, self).visit_select(*args, **kwargs)

def visit_column(
self, column, add_to_result_map=None, include_table=True, **kwargs
):
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ def readme():
platforms="Posix; MacOS X; Windows",
install_requires=[
"sqlalchemy>=1.1.9,<1.4.0dev",
"google-auth>=1.2.0,<2.0dev",
"google-auth>=1.14.0,<2.0dev", # Work around pip wack.
"google-cloud-bigquery>=1.12.0",
"google-api-core>=1.19.1", # Work-around bug in cloud core deps.
"future",
],
python_requires=">=3.6, <3.10",
Expand Down
2 changes: 1 addition & 1 deletion testing/constraints-3.6.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
#
# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
sqlalchemy==1.1.9
google-auth==1.2.0
google-auth==1.14.0
google-cloud-bigquery==1.12.0
16 changes: 16 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import mock
import pytest
import sqlalchemy

import fauxdbi


@pytest.fixture()
def faux_conn():
with mock.patch(
"google.cloud.bigquery.dbapi.connection.Connection", fauxdbi.Connection
):
engine = sqlalchemy.create_engine("bigquery://myproject/mydataset")
conn = engine.connect()
yield conn
conn.close()
98 changes: 98 additions & 0 deletions tests/unit/fauxdbi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import google.api_core.exceptions
import google.cloud.bigquery.schema
import google.cloud.bigquery.table
import contextlib
import sqlite3


class Connection:

connection = None

def __init__(self, client=None, bqstorage_client=None):
# share a single connection:
if self.connection is None:
self.__class__.connection = sqlite3.connect(":memory:")
self._client = FauxClient(client, self.connection)

def cursor(self):
return Cursor(self.connection)

def commit(self):
pass

def rollback(self):
pass

def close(self):
self.connection.close()


class Cursor:

arraysize = 1

def __init__(self, connection):
self.connection = connection
self.cursor = connection.cursor()

def execute(self, operation, parameters=None):
if parameters:
parameters = {
name: "null" if value is None else repr(value)
for name, value in parameters.items()
}
operation %= parameters
self.cursor.execute(operation, parameters)
self.description = self.cursor.description
self.rowcount = self.cursor.rowcount

def executemany(self, operation, parameters_list):
for parameters in parameters_list:
self.execute(operation, parameters)

def close(self):
self.cursor.close()

def fetchone(self):
return self.cursor.fetchone()

def fetchmany(self, size=None):
self.cursor.fetchmany(size or self.arraysize)

def fetchall(self):
return self.cursor.fetchall()

def setinputsizes(self, sizes):
pass

def setoutputsize(self, size, column=None):
pass


class FauxClient:
def __init__(self, client, connection):
self._client = client
self.project = client.project
self.connection = connection

def get_table(self, table_ref):
table_name = table_ref.table_id
with contextlib.closing(self.connection.cursor()) as cursor:
cursor.execute(
f"select name from sqlite_master"
f" where type='table' and name='{table_name}'"
)
if list(cursor):
cursor.execute("PRAGMA table_info('{table_name}')")
schema = [
google.cloud.bigquery.schema.SchemaField(
name=name,
field_type=type_,
mode="REQUIRED" if notnull else "NULLABLE",
)
for cid, name, type_, notnull, dflt_value, pk in cursor
]
return google.cloud.bigquery.table.Table(table_ref, schema)
else:
raise google.api_core.exceptions.NotFound(table_ref)
11 changes: 11 additions & 0 deletions tests/unit/test_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sqlalchemy


def test_labels_not_forced(faux_conn):
metadata = sqlalchemy.MetaData()
table = sqlalchemy.Table(
"some_table", metadata, sqlalchemy.Column("id", sqlalchemy.Integer)
)
metadata.create_all(faux_conn.engine)
result = faux_conn.execute(sqlalchemy.select([table.c.id]))
assert result.keys() == ["id"] # Look! Just the column name!