mirror of
https://github.com/apache/superset.git
synced 2026-07-16 19:55:39 +00:00
Compare commits
7 Commits
chore/i18n
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e25c1eaed | ||
|
|
4048ffe04a | ||
|
|
634291c4af | ||
|
|
a3cc2e7b24 | ||
|
|
1d1fa29fc8 | ||
|
|
d84ebe4ff0 | ||
|
|
f882bc92fb |
@@ -250,6 +250,28 @@ def json_to_dict(json_str: str) -> dict[Any, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
UUID_NATIVE_TYPE_RE: re.Pattern[str] = re.compile(
|
||||
r"\b(uuid|uniqueidentifier)\b", re.IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
def is_uuid_native_type(native_type: Optional[str]) -> bool:
|
||||
"""
|
||||
Return True if a native column type represents a UUID.
|
||||
|
||||
Engines such as PostgreSQL and ClickHouse expose native UUID column types
|
||||
(e.g. ``UUID``, ``Nullable(UUID)``) that map to ``GenericDataType.STRING``
|
||||
yet reject LIKE/ILIKE against the raw column, so these columns need an
|
||||
explicit cast to string before pattern matching. SQL Server's equivalent
|
||||
native type is ``uniqueidentifier``, which is matched too. The match is
|
||||
on whole words so unrelated types that merely contain one of these as a
|
||||
substring (e.g. a hypothetical ``uuidish`` type) aren't misclassified.
|
||||
"""
|
||||
return native_type is not None and bool(
|
||||
UUID_NATIVE_TYPE_RE.search(native_type.strip())
|
||||
)
|
||||
|
||||
|
||||
def convert_uuids(obj: Any) -> Any:
|
||||
"""
|
||||
Convert UUID objects to str so we can use yaml.safe_dump
|
||||
@@ -3862,22 +3884,23 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
elif op in {
|
||||
utils.FilterOperator.ILIKE,
|
||||
utils.FilterOperator.LIKE,
|
||||
utils.FilterOperator.NOT_LIKE,
|
||||
utils.FilterOperator.NOT_ILIKE,
|
||||
}:
|
||||
if target_generic_type != GenericDataType.STRING:
|
||||
# Native UUID columns report GenericDataType.STRING but
|
||||
# reject LIKE/ILIKE without a cast (see issue #41795)
|
||||
needs_string_cast_for_like: bool = (
|
||||
target_generic_type != GenericDataType.STRING
|
||||
or is_uuid_native_type(col_type)
|
||||
)
|
||||
if needs_string_cast_for_like:
|
||||
sqla_col = sa.cast(sqla_col, sa.String)
|
||||
|
||||
if op == utils.FilterOperator.LIKE:
|
||||
target_clause_list.append(sqla_col.like(eq))
|
||||
else:
|
||||
elif op == utils.FilterOperator.ILIKE:
|
||||
target_clause_list.append(sqla_col.ilike(eq))
|
||||
elif op in {
|
||||
utils.FilterOperator.NOT_LIKE,
|
||||
utils.FilterOperator.NOT_ILIKE,
|
||||
}:
|
||||
if target_generic_type != GenericDataType.STRING:
|
||||
sqla_col = sa.cast(sqla_col, sa.String)
|
||||
|
||||
if op == utils.FilterOperator.NOT_LIKE:
|
||||
elif op == utils.FilterOperator.NOT_LIKE:
|
||||
target_clause_list.append(sqla_col.not_like(eq))
|
||||
else:
|
||||
target_clause_list.append(sqla_col.not_ilike(eq))
|
||||
|
||||
@@ -29,7 +29,8 @@ from pytest_mock import MockerFixture
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
from sqlalchemy.sql.elements import Cast, ColumnElement
|
||||
from sqlalchemy.sql.visitors import iterate
|
||||
|
||||
from superset.superset_typing import AdhocColumn, AdhocMetric, OrderBy
|
||||
from superset.utils.core import FilterOperator, GenericDataType
|
||||
@@ -3720,3 +3721,82 @@ def test_simple_metric_quotes_column_requiring_quoting(database: Database) -> No
|
||||
assert f"SUM({column_name})" not in rendered, (
|
||||
f"Column requiring quoting was emitted unquoted: {rendered}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"native_type",
|
||||
[
|
||||
"UUID",
|
||||
"uuid",
|
||||
"Nullable(UUID)",
|
||||
"uniqueidentifier",
|
||||
"LowCardinality(UUID)",
|
||||
"LowCardinality(Nullable(UUID))",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"op",
|
||||
["LIKE", "ILIKE", "NOT LIKE", "NOT ILIKE"],
|
||||
)
|
||||
def test_like_filter_on_uuid_column_casts_to_string(
|
||||
database: Database, native_type: str, op: str
|
||||
) -> None:
|
||||
"""
|
||||
LIKE-family filters on native UUID columns must cast the column to string.
|
||||
|
||||
UUID columns map to ``GenericDataType.STRING``, so the generic-type guard
|
||||
alone skips the string cast — but engines such as PostgreSQL and ClickHouse
|
||||
reject LIKE/ILIKE against a raw UUID column (issue #41795: table chart
|
||||
server-pagination search fails with e.g. "Illegal type UUID of argument of
|
||||
function ilike"). The native column type must force the cast.
|
||||
"""
|
||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||
|
||||
table = SqlaTable(
|
||||
database=database,
|
||||
schema=None,
|
||||
table_name="t",
|
||||
columns=[TableColumn(column_name="event_id", type=native_type)],
|
||||
)
|
||||
|
||||
result = table.get_sqla_query(
|
||||
columns=["event_id"],
|
||||
metrics=[],
|
||||
extras={},
|
||||
filter=[{"col": "event_id", "op": op, "val": "abc%"}],
|
||||
granularity=None,
|
||||
is_timeseries=False,
|
||||
orderby=[],
|
||||
)
|
||||
whereclause: ColumnElement = result.sqla_query.whereclause
|
||||
assert any(isinstance(node, Cast) for node in iterate(whereclause)), (
|
||||
f"Expected a Cast node in the filter expression: {whereclause}"
|
||||
)
|
||||
|
||||
|
||||
def test_like_filter_on_string_column_does_not_cast(database: Database) -> None:
|
||||
"""
|
||||
LIKE-family filters on plain string columns must not add a redundant cast.
|
||||
"""
|
||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||
|
||||
table = SqlaTable(
|
||||
database=database,
|
||||
schema=None,
|
||||
table_name="t",
|
||||
columns=[TableColumn(column_name="b", type="TEXT")],
|
||||
)
|
||||
|
||||
result = table.get_sqla_query(
|
||||
columns=["b"],
|
||||
metrics=[],
|
||||
extras={},
|
||||
filter=[{"col": "b", "op": "ILIKE", "val": "abc%"}],
|
||||
granularity=None,
|
||||
is_timeseries=False,
|
||||
orderby=[],
|
||||
)
|
||||
whereclause: ColumnElement = result.sqla_query.whereclause
|
||||
assert not any(isinstance(node, Cast) for node in iterate(whereclause)), (
|
||||
f"Unexpected Cast node in the filter expression: {whereclause}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user