You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/10 18:12:51 UTC

(superset) branch master updated: fix(sqla): preserve float precision when mixing int/float values in IN filters (#42752)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new b98e1571043 fix(sqla): preserve float precision when mixing int/float values in IN filters (#42752)
b98e1571043 is described below

commit b98e1571043aa1af4c2aab0e6e0de401fae525f6
Author: varadendrasimha511 <10...@users.noreply.github.com>
AuthorDate: Mon Aug 10 23:42:43 2026 +0530

    fix(sqla): preserve float precision when mixing int/float values in IN filters (#42752)
---
 superset/models/helpers.py              |  13 +++++
 tests/unit_tests/models/helpers_test.py | 100 ++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+)

diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index 671c07f3026..aeb9e79a21a 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -4308,6 +4308,18 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                         raise QueryObjectValidationError(
                             _("Filter value list cannot be empty")
                         )
+
+                    # Normalize mixed int/float values before binding, since
+                    # SQLAlchemy may infer the bind parameter type from the
+                    # first element and silently truncate other values
+                    # (see #33206)
+                    if target_generic_type == utils.GenericDataType.NUMERIC and any(
+                        isinstance(v, float) for v in eq
+                    ):
+                        eq = [
+                            float(v) if isinstance(v, (int, float)) else v for v in eq
+                        ]
+
                     if len(eq) > len(
                         eq_without_none := [x for x in eq if x is not None]
                     ):
@@ -4318,6 +4330,7 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
                             cond = is_null_cond
                     else:
                         cond = sqla_col.in_(eq)
+
                     if op == utils.FilterOperator.NOT_IN:
                         cond = ~cond
                     target_clause_list.append(cond)
diff --git a/tests/unit_tests/models/helpers_test.py b/tests/unit_tests/models/helpers_test.py
index 49f122b60d2..a7156b795b9 100644
--- a/tests/unit_tests/models/helpers_test.py
+++ b/tests/unit_tests/models/helpers_test.py
@@ -2754,6 +2754,106 @@ def test_calculated_column_non_boolean_filter_is_parenthesized(
     )
 
 
+def test_get_sqla_query_in_filter_preserves_float_precision(
+    database: Database,
+) -> None:
+    """
+    Test that mixing integer and float values in an "IN" filter does not
+    truncate the float value's decimal precision when the compiled query
+    binds parameters (see #33206).
+    """
+    from superset.connectors.sqla.models import SqlaTable, TableColumn
+
+    table = SqlaTable(
+        database=database,
+        schema=None,
+        table_name="t",
+        columns=[
+            TableColumn(column_name="global_sales", type="FLOAT"),
+        ],
+    )
+
+    sqla_query = table.get_sqla_query(
+        columns=["global_sales"],
+        filter=[
+            {
+                "col": "global_sales",
+                "op": "IN",
+                "val": [33, 29.02],
+            },
+        ],
+        extras={},
+        is_timeseries=False,
+        metrics=[],
+    )
+
+    with database.get_sqla_engine() as engine:
+        sql = str(
+            sqla_query.sqla_query.compile(
+                dialect=engine.dialect,
+                compile_kwargs={"literal_binds": True},
+            )
+        )
+
+    # The decimal value must retain its precision in the compiled SQL,
+    # rather than being silently truncated to the integer 29.
+    assert "29.02" in sql, (
+        f"Expected float value 29.02 to be preserved in the IN clause, "
+        f"but it was likely truncated. Generated SQL: {sql}"
+    )
+
+
+def test_get_sqla_query_in_filter_preserves_float_precision_with_null(
+    database: Database,
+) -> None:
+    """
+    Test that mixing integer and float values with a NULL in an "IN" filter
+    does not truncate the float value's decimal precision, even though this
+    triggers the separate `eq_without_none` code path (see #33206).
+    """
+    from superset.connectors.sqla.models import SqlaTable, TableColumn
+
+    table = SqlaTable(
+        database=database,
+        schema=None,
+        table_name="t",
+        columns=[
+            TableColumn(column_name="global_sales", type="FLOAT"),
+        ],
+    )
+
+    sqla_query = table.get_sqla_query(
+        columns=["global_sales"],
+        filter=[
+            {
+                "col": "global_sales",
+                "op": "IN",
+                "val": [33, 29.02, None],  # type: ignore[list-item]
+            },
+        ],
+        extras={},
+        is_timeseries=False,
+        metrics=[],
+    )
+
+    with database.get_sqla_engine() as engine:
+        sql = str(
+            sqla_query.sqla_query.compile(
+                dialect=engine.dialect,
+                compile_kwargs={"literal_binds": True},
+            )
+        )
+
+    # The decimal value must retain its precision in the compiled SQL,
+    # rather than being silently truncated to the integer 29, even when
+    # the filter list also contains a NULL value.
+    assert "29.02" in sql, (
+        f"Expected float value 29.02 to be preserved in the IN clause "
+        f"even with a NULL present, but it was likely truncated. "
+        f"Generated SQL: {sql}"
+    )
+
+
 def test_multiple_calculated_columns_each_parenthesized(
     database: Database,
 ) -> None: