From 5fbc809417e1077deb2eac3f621d270819cf7ff2 Mon Sep 17 00:00:00 2001 From: DG-Elom Date: Wed, 14 Oct 2026 12:02:25 +0200 Subject: [PATCH] Exact filters match integer columns without type affinity, closes #2073 --- datasette/filters.py | 20 +++++++++++++++++++- tests/test_facets.py | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/datasette/filters.py b/datasette/filters.py index 0499d086..4754b676 100644 --- a/datasette/filters.py +++ b/datasette/filters.py @@ -259,6 +259,24 @@ class TemplatedFilter(Filter): return template.format(c=column, v=value) +class ExactFilter(TemplatedFilter): + def where_clause(self, table, column, value, param_counter): + sql, param = super().where_clause(table, column, value, param_counter) + try: + as_int = int(value) + except ValueError: + return sql, param + if str(as_int) != value: + return sql, param + # Columns without type affinity (such as expressions in a view) do not + # convert "1" to 1 for the comparison, so match both representations + quoted = _quote_sqlite_identifier(column) + return ( + f"{quoted} in (:p{param_counter}, :p{param_counter + 1})", + [value, as_int], + ) + + def _quote_sqlite_identifier(identifier): # Preserve the historic always-quoted SQL generated by TemplatedFilter. escaped = escape_sqlite(identifier) @@ -305,7 +323,7 @@ class Filters: _filters = ( [ # key, display, sql_template, human_template, format=, numeric=, no_argument= - TemplatedFilter( + ExactFilter( "exact", "=", "{c} = :{p}", diff --git a/tests/test_facets.py b/tests/test_facets.py index 10325f9a..03a39fe9 100644 --- a/tests/test_facets.py +++ b/tests/test_facets.py @@ -829,3 +829,25 @@ async def test_facet_only_considers_first_x_rows(): assert data2["suggested_facets"] == [] finally: Facet.suggest_consider = original_suggest_consider + + +@pytest.mark.asyncio +async def test_facet_selection_against_integer_column_in_view(): + # https://github.com/simonw/datasette/issues/2073 + # View columns computed from expressions have no column affinity, so + # comparing them to the string "1" from the querystring does not match 1 + ds = Datasette() + db = ds.add_memory_database("test_facet_integer_view") + await db.execute_write("create table t (id integer primary key, n integer)") + await db.execute_write_many( + "insert into t (n) values (:n)", [{"n": 1}, {"n": 1}, {"n": 2}] + ) + await db.execute_write("create view v as select id, n + 0 as n from t") + response = await ds.client.get( + "/test_facet_integer_view/v.json?_facet=n&n=1&_extra=facet_results" + ) + assert response.status_code == 200 + data = response.json() + assert [row["id"] for row in data["rows"]] == [1, 2] + results = data["facet_results"]["results"]["n"]["results"] + assert [(r["value"], r["count"], r["selected"]) for r in results] == [(1, 2, True)]