fix(sql): preserve multi-arg DISTINCT in sanitize_clause and format (#39340)

This commit is contained in:
Jean Dupuis
2026-06-19 21:02:50 +01:00
committed by GitHub
parent f1504611fd
commit b2e5f80db2
2 changed files with 103 additions and 11 deletions

View File

@@ -32,9 +32,11 @@ from sqlglot import exp
from sqlglot.dialects.dialect import (
Dialect,
Dialects,
DialectType,
)
from sqlglot.dialects.singlestore import SingleStore
from sqlglot.errors import ParseError
from sqlglot.generator import Generator
from sqlglot.optimizer.pushdown_predicates import (
pushdown_predicates,
)
@@ -137,6 +139,32 @@ class CTASMethod(enum.Enum):
VIEW = enum.auto()
def _normalized_generator(
dialect_name: DialectType,
*,
pretty: bool,
comments: bool,
) -> Generator:
"""
Generator that preserves multi-argument DISTINCT expressions.
Build a sqlglot generator that preserves user-written multi-argument
DISTINCT expressions verbatim. Postgres, Presto, Trino, and DuckDB
set ``MULTI_ARG_DISTINCT = False`` to emulate the unsupported
``COUNT(DISTINCT a, b)`` idiom via a ``CASE WHEN`` row-expression, which
silently corrupts user-defined aggregates that natively accept multiple
arguments. Superset's sanitize / format paths normalize user SQL — they
do not transpile — so the emulation is undesirable here.
"""
dialect = Dialect.get_or_raise(dialect_name)
normalized_cls = type(
f"Normalized{dialect.generator_class.__name__}",
(dialect.generator_class,),
{"MULTI_ARG_DISTINCT": True},
)
return normalized_cls(dialect=dialect, pretty=pretty, comments=comments)
class RLSMethod(enum.Enum):
"""
Methods for enforcing RLS.
@@ -911,12 +939,11 @@ class SQLStatement(BaseSQLStatement[exp.Expression]):
"""
Pretty-format the SQL statement.
"""
return Dialect.get_or_raise(self._dialect).generate(
self._parsed,
copy=True,
comments=comments,
return _normalized_generator(
self._dialect,
pretty=True,
)
comments=comments,
).generate(self._parsed, copy=True)
def get_settings(self) -> dict[str, str | bool]:
"""
@@ -1817,14 +1844,13 @@ def sanitize_clause(clause: str, engine: str) -> str:
"""
try:
statement = SQLStatement(clause, engine)
dialect = SQLGLOT_DIALECTS.get(engine)
from sqlglot.dialects.dialect import Dialect
return Dialect.get_or_raise(dialect).generate(
return _normalized_generator(
SQLGLOT_DIALECTS.get(engine),
pretty=False,
comments=True,
).generate(
statement._parsed, # pylint: disable=protected-access
copy=True,
comments=True,
pretty=False,
)
except SupersetParseError as ex:
raise QueryClauseValidationException(f"Invalid SQL clause: {clause}") from ex