mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
fix(BigQuery): explicitly quote columns in select_star (#16822)
* fix (BigQuery): explicitly quote columns in select_star * Fix test * Fix SELECT * in BQ * Add unit tests * Remove type changes
This commit is contained in:
190
tests/unit_tests/db_engine_specs/test_bigquery.py
Normal file
190
tests/unit_tests/db_engine_specs/test_bigquery.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# pylint: disable=unused-argument, import-outside-toplevel, protected-access
|
||||
|
||||
from flask.ctx import AppContext
|
||||
from pytest_mock import MockFixture
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.dialects.sqlite.base import SQLiteDialect
|
||||
from sqlalchemy.sql import sqltypes
|
||||
|
||||
|
||||
def test_get_fields(app_context: AppContext) -> None:
|
||||
"""
|
||||
Test the custom ``_get_fields`` method.
|
||||
|
||||
The method adds custom labels (aliases) to the columns to prevent
|
||||
collision when referencing record fields. Eg, if we had these two
|
||||
columns:
|
||||
|
||||
name STRING
|
||||
project STRUCT<name STRING>
|
||||
|
||||
One could write this query:
|
||||
|
||||
SELECT
|
||||
`name`,
|
||||
`project`.`name`
|
||||
FROM
|
||||
the_table
|
||||
|
||||
But then both columns would get aliased as "name".
|
||||
|
||||
The custom method will replace the fields so that the final query
|
||||
looks like this:
|
||||
|
||||
SELECT
|
||||
`name` AS `name`,
|
||||
`project`.`name` AS project__name
|
||||
FROM
|
||||
the_table
|
||||
|
||||
"""
|
||||
from superset.db_engine_specs.bigquery import BigQueryEngineSpec
|
||||
|
||||
columns = [{"name": "limit"}, {"name": "name"}, {"name": "project.name"}]
|
||||
fields = BigQueryEngineSpec._get_fields(columns)
|
||||
|
||||
# generic SQL
|
||||
query = select(fields)
|
||||
assert (
|
||||
str(query)
|
||||
== 'SELECT "limit" AS "limit", name AS name, "project.name" AS project__name'
|
||||
)
|
||||
|
||||
# BigQuery-specific SQL
|
||||
try:
|
||||
from pybigquery.sqlalchemy_bigquery import BigQueryDialect
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
|
||||
assert str(query.compile(dialect=BigQueryDialect())) == (
|
||||
"SELECT `limit` AS `limit`, `name` AS `name`, "
|
||||
"`project`.`name` AS `project__name`"
|
||||
)
|
||||
|
||||
|
||||
def test_select_star(mocker: MockFixture, app_context: AppContext) -> None:
|
||||
"""
|
||||
Test the ``select_star`` method.
|
||||
|
||||
The method removes pseudo-columns from structures inside arrays. While these
|
||||
pseudo-columns show up as "columns" for metadata reasons, we can't select them
|
||||
in the query, as opposed to fields from non-array structures.
|
||||
"""
|
||||
from superset.db_engine_specs.bigquery import BigQueryEngineSpec
|
||||
|
||||
cols = [
|
||||
{
|
||||
"name": "trailer",
|
||||
"type": sqltypes.ARRAY(sqltypes.JSON()),
|
||||
"nullable": True,
|
||||
"comment": None,
|
||||
"default": None,
|
||||
"precision": None,
|
||||
"scale": None,
|
||||
"max_length": None,
|
||||
},
|
||||
{
|
||||
"name": "trailer.key",
|
||||
"type": sqltypes.String(),
|
||||
"nullable": True,
|
||||
"comment": None,
|
||||
"default": None,
|
||||
"precision": None,
|
||||
"scale": None,
|
||||
"max_length": None,
|
||||
},
|
||||
{
|
||||
"name": "trailer.value",
|
||||
"type": sqltypes.String(),
|
||||
"nullable": True,
|
||||
"comment": None,
|
||||
"default": None,
|
||||
"precision": None,
|
||||
"scale": None,
|
||||
"max_length": None,
|
||||
},
|
||||
{
|
||||
"name": "trailer.email",
|
||||
"type": sqltypes.String(),
|
||||
"nullable": True,
|
||||
"comment": None,
|
||||
"default": None,
|
||||
"precision": None,
|
||||
"scale": None,
|
||||
"max_length": None,
|
||||
},
|
||||
]
|
||||
|
||||
# mock the database so we can compile the query
|
||||
database = mocker.MagicMock()
|
||||
database.compile_sqla_query = lambda query: str(
|
||||
query.compile(dialect=SQLiteDialect())
|
||||
)
|
||||
|
||||
# use SQLite dialect so we don't need the BQ dependency
|
||||
engine = mocker.MagicMock()
|
||||
engine.dialect = SQLiteDialect()
|
||||
|
||||
sql = BigQueryEngineSpec.select_star(
|
||||
database=database,
|
||||
table_name="my_table",
|
||||
engine=engine,
|
||||
schema=None,
|
||||
limit=100,
|
||||
show_cols=True,
|
||||
indent=True,
|
||||
latest_partition=False,
|
||||
cols=cols,
|
||||
)
|
||||
assert (
|
||||
sql
|
||||
== """SELECT trailer AS trailer
|
||||
FROM my_table
|
||||
LIMIT ?
|
||||
OFFSET ?"""
|
||||
)
|
||||
|
||||
# BigQuery-specific SQL
|
||||
try:
|
||||
from pybigquery.sqlalchemy_bigquery import BigQueryDialect
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
|
||||
database.compile_sqla_query = lambda query: str(
|
||||
query.compile(dialect=BigQueryDialect())
|
||||
)
|
||||
engine.dialect = BigQueryDialect()
|
||||
|
||||
sql = BigQueryEngineSpec.select_star(
|
||||
database=database,
|
||||
table_name="my_table",
|
||||
engine=engine,
|
||||
schema=None,
|
||||
limit=100,
|
||||
show_cols=True,
|
||||
indent=True,
|
||||
latest_partition=False,
|
||||
cols=cols,
|
||||
)
|
||||
assert (
|
||||
sql
|
||||
== """SELECT `trailer` AS `trailer`
|
||||
FROM `my_table`
|
||||
LIMIT :param_1"""
|
||||
)
|
||||
Reference in New Issue
Block a user