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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 0 additions & 9 deletions pybigquery/sqlalchemy_bigquery.py
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
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
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
@@ -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
@@ -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
@@ -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!