Files
superset2/tests/unit_tests/models/multi_space_column_names_test.py
2026-07-07 08:53:20 -07:00

165 lines
5.2 KiB
Python

# 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.
"""
Regression tests for https://github.com/apache/superset/issues/32879.
Physical columns whose names contain multiple consecutive spaces
(e.g. ``Eladó 1``) used to be routed through the sqlparse-based
``validate_adhoc_subquery`` as raw, unquoted SQL, which misparsed the
name and rejected the query with "Custom SQL fields cannot contain
sub-queries.". The SIP-117 migration to the sqlglot-based parser and the
column-resolution logic in ``get_sqla_query`` (registered columns are
rendered as properly quoted SQLAlchemy columns instead of raw adhoc SQL)
fixed this; these tests pin the fixed behavior so it cannot regress.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from pytest_mock import MockerFixture
from sqlalchemy.orm.session import Session
if TYPE_CHECKING:
from superset.models.core import Database
MULTI_SPACE_COLUMN = "Eladó 1"
@pytest.fixture
def database(session: Session) -> Database:
from superset.connectors.sqla.models import SqlaTable
from superset.models.core import Database
SqlaTable.metadata.create_all(session.get_bind())
return Database(database_name="db", sqlalchemy_uri="sqlite://")
def _compile(database: Database, sqla_query: object) -> str:
with database.get_sqla_engine() as engine:
return str(
sqla_query.compile( # type: ignore[attr-defined]
dialect=engine.dialect,
compile_kwargs={"literal_binds": True},
)
)
def test_get_sqla_query_groupby_column_with_multiple_spaces(
database: Database,
) -> None:
"""
A registered physical column with multiple consecutive spaces in its
name can be used as a chart dimension (the exact scenario from
issue #32879) without tripping the adhoc-subquery validation.
"""
from superset.connectors.sqla.models import SqlaTable, TableColumn
table = SqlaTable(
database=database,
schema=None,
table_name="t",
columns=[
TableColumn(column_name="id", type="INTEGER"),
TableColumn(column_name=MULTI_SPACE_COLUMN, type="TEXT"),
],
)
# Must not raise SupersetSecurityException ("Custom SQL fields cannot
# contain sub-queries.") as it did on 4.1.x.
sqla_query = table.get_sqla_query(
groupby=[MULTI_SPACE_COLUMN],
metrics=[],
is_timeseries=False,
row_limit=100,
)
sql = _compile(database, sqla_query.sqla_query)
# The column is emitted as a single quoted identifier with both spaces
# preserved, so the database resolves the real column.
assert f'"{MULTI_SPACE_COLUMN}"' in sql
assert "GROUP BY" in sql.upper()
def test_get_sqla_query_select_column_with_multiple_spaces(
database: Database,
) -> None:
"""
The raw-columns path (drill to detail / samples) also handles a
multi-space physical column without raising.
"""
from superset.connectors.sqla.models import SqlaTable, TableColumn
table = SqlaTable(
database=database,
schema=None,
table_name="t",
columns=[
TableColumn(column_name="id", type="INTEGER"),
TableColumn(column_name=MULTI_SPACE_COLUMN, type="TEXT"),
],
)
sqla_query = table.get_sqla_query(
columns=["id", MULTI_SPACE_COLUMN],
metrics=None,
is_timeseries=False,
row_limit=100,
)
sql = _compile(database, sqla_query.sqla_query)
assert f'"{MULTI_SPACE_COLUMN}"' in sql
@pytest.mark.parametrize(
"engine,expression",
[
("sqlite", f'"{MULTI_SPACE_COLUMN}"'),
("postgresql", f'"{MULTI_SPACE_COLUMN}"'),
("mssql", f"[{MULTI_SPACE_COLUMN}]"),
],
)
def test_validate_adhoc_subquery_accepts_quoted_multi_space_identifier(
mocker: MockerFixture,
engine: str,
expression: str,
) -> None:
"""
The sqlglot-based ``validate_adhoc_subquery`` accepts a quoted
identifier containing multiple consecutive spaces: it is not a
sub-query, so no SupersetSecurityException is raised and the SQL is
returned unchanged.
"""
from superset.models.helpers import validate_adhoc_subquery
# Only consulted for RLS when a subquery is detected, which must not
# happen here.
database = mocker.MagicMock()
assert (
validate_adhoc_subquery(
expression,
database,
None,
"",
engine,
)
== expression
)