Compare commits

...

7 Commits

Author SHA1 Message Date
Evan
7e25c1eaed test: add LowCardinality-wrapped UUID native-type cases
Confirms the UUID native-type regex still matches ClickHouse's
LowCardinality(UUID) / LowCardinality(Nullable(UUID)) wrapping, per
review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 07:55:53 -07:00
Evan
4048ffe04a fix: match SQL Server uniqueidentifier as a UUID native type for LIKE casts
Extend UUID_NATIVE_TYPE_RE to also match uniqueidentifier so MSSQL GUID
columns get the same string cast as PostgreSQL/ClickHouse UUID columns
before LIKE/ILIKE filtering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:34:14 -07:00
Evan
634291c4af fix: annotate remaining whereclause local in NOT ILIKE test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 08:38:31 -07:00
Evan
a3cc2e7b24 fix: annotate remaining new locals per type-hint rule
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 03:52:53 -07:00
Evan
1d1fa29fc8 fix: add explicit type annotation to UUID_NATIVE_TYPE_RE
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 03:52:53 -07:00
Evan
d84ebe4ff0 fix: dedupe LIKE cast condition, tighten UUID type match, assert on Cast node
Merge the LIKE/ILIKE and NOT LIKE/NOT ILIKE branches so the cast condition
is computed once, match the native UUID type on a whole word instead of a
bare substring, and assert on the SQLAlchemy Cast node instead of the
literal "CAST" string so the test isn't tied to a particular dialect's
rendering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 03:52:52 -07:00
Evan
f882bc92fb fix(sqla): cast native UUID columns to string for LIKE/ILIKE filters
Table chart server-pagination search builds an ILIKE filter against the
searched column. Native UUID columns (PostgreSQL, ClickHouse) map to
GenericDataType.STRING, so the existing not-a-string guard skipped the
cast and the raw ILIKE hit the UUID column type, which the database
rejects (e.g. ClickHouse: "Illegal type UUID of argument of function
ilike"). Detect UUID native types and force the string cast so
searching UUID columns works.

Fixes #41795

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 03:52:46 -07:00
2 changed files with 114 additions and 11 deletions

View File

@@ -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))

View File

@@ -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}"
)