fix: type probing (#38889)

This commit is contained in:
Beto Dealmeida
2026-03-26 13:06:49 -04:00
committed by GitHub
parent 95820fb9e6
commit 8983edea66
5 changed files with 138 additions and 1 deletions

View File

@@ -1856,3 +1856,112 @@ def test_extras_having_is_parenthesized(
assert "(COUNT(*) > 0 OR 1 = 1)" in sql, (
f"extras.having should be wrapped in parentheses. Generated SQL: {sql}"
)
def _run_probe(
database: Database,
type_probe_needs_row: bool = False,
) -> str:
"""
Run adhoc_column_to_sqla with a mocked get_columns_description and
return the SQL that was passed to it.
``type_probe_needs_row`` is patched onto the database's real engine spec
class so every other method keeps working normally.
"""
from unittest.mock import patch
from superset.connectors.sqla.models import SqlaTable, TableColumn
table = SqlaTable(
database=database,
schema=None,
table_name="t",
columns=[TableColumn(column_name="a", type="INTEGER")],
)
adhoc_col: AdhocColumn = {
"sqlExpression": "round(a / 50) * 50 / 1000",
"label": "Duration",
"columnType": "BASE_AXIS",
"timeGrain": "P1D",
}
captured: list[str] = []
def fake_get_columns_description(
db: object,
catalog: object,
schema: object,
sql: str,
) -> list[dict[str, object]]:
captured.append(sql)
return [
{
"column_name": "Duration",
"name": "Duration",
"type": "FLOAT",
"is_dttm": False,
}
]
spec_cls = database.db_engine_spec
with (
patch(
"superset.connectors.sqla.models.get_columns_description",
side_effect=fake_get_columns_description,
),
patch.object(spec_cls, "type_probe_needs_row", type_probe_needs_row),
):
result = table.adhoc_column_to_sqla(adhoc_col)
assert result is not None
assert len(captured) == 1, "Expected exactly one type-probe query"
return captured[0]
def test_adhoc_column_type_probe_uses_where_false(database: Database) -> None:
"""
Most engines populate cursor.description from query-plan metadata, so the
probe uses WHERE FALSE — zero rows read, no table scan.
SQLAlchemy renders sa.false() differently per dialect:
- Most databases: WHERE false
- SQLite: WHERE 0 = 1
"""
probe_sql = _run_probe(database, type_probe_needs_row=False).lower()
always_false_patterns = ("where false", "where 0 = 1")
assert any(p in probe_sql for p in always_false_patterns), (
f"Probe query should use a WHERE false condition to avoid scanning the "
f"table, but got: {probe_sql}"
)
assert "limit" not in probe_sql, (
f"WHERE false probe must not contain LIMIT, but got: {probe_sql}"
)
def test_adhoc_column_type_probe_uses_limit_1_for_row_dependent_engines(
database: Database,
) -> None:
"""
Druid and Pinot build cursor.description by inspecting the first returned
row. WHERE FALSE yields no rows, so description stays None. These engines
must fall back to LIMIT 1.
"""
from superset.db_engine_specs.druid import DruidEngineSpec
from superset.db_engine_specs.pinot import PinotEngineSpec
for spec_cls in (DruidEngineSpec, PinotEngineSpec):
assert spec_cls.type_probe_needs_row is True, (
f"{spec_cls.__name__} must declare type_probe_needs_row = True"
)
probe_sql = _run_probe(database, type_probe_needs_row=True).lower()
assert "limit 1" in probe_sql, (
f"Row-dependent engines: probe should use LIMIT 1, got: {probe_sql}"
)
always_false_patterns = ("where false", "where 0 = 1")
assert not any(p in probe_sql for p in always_false_patterns), (
f"Row-dependent engines: probe must NOT use WHERE false, got: {probe_sql}"
)