This commit is contained in:
David Elom Gnaglo 2026-10-14 10:11:17 +00:00 • committed by GitHub
commit 56fea35d37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 1 deletions

View file

@ -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 or not -(2**63) <= as_int < 2**63:
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}",

View file

@ -829,3 +829,39 @@ 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)]
@pytest.mark.asyncio
async def test_exact_filter_integer_outside_sqlite_range():
# Values beyond SQLite 64-bit integers must not be bound as integers
ds = Datasette()
db = ds.add_memory_database("test_exact_filter_big_int")
await db.execute_write("create table t (id integer primary key, code text)")
await db.execute_write("insert into t (code) values (?)", ["9223372036854775808"])
response = await ds.client.get(
"/test_exact_filter_big_int/t.json?code=9223372036854775808"
)
assert response.status_code == 200
assert [row["id"] for row in response.json()["rows"]] == [1]