Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions beets/dbcore/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ def match(self, obj: Model) -> bool:

if self.point is not None:
return value == self.point
if value is None:
# Nullable types (``NullInteger``/``NullFloat``) use ``None`` as
# their null value, so the field may be missing on some objects.
# ``col_clause`` compares against NULL in SQL, which is never true,
# so a range never matches a null value.
return False
if self.rangemin is not None and value < self.rangemin:
return False
if self.rangemax is not None and value > self.rangemax:
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ Bug fixes
valid date/time string" error instead of crashing with an uncaught
``KeyError``. A ``|`` was being accepted as a relative-date unit due to a
regular expression character-class typo.
- A range query against a nullable numeric field (such as ``rg_track_gain`` on
an item without ReplayGain data, or ``album_id`` on a singleton) no longer
crashes with a ``TypeError``. The range now matches nothing, as it already did
on the SQL side.

..
For plugin developers
Expand Down
12 changes: 12 additions & 0 deletions test/dbcore/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,18 @@ def test_match(self, item, q, should_match):
assert q.match(item) == should_match
assert not NotQuery(q).match(item) == should_match

@pytest.mark.parametrize("pattern", ["-10..0", "0..", "..0"])
def test_numeric_range_match_null_field(self, pattern):
"""A range never matches a null value, as in the SQL clause.

Nullable types (``NullFloat``/``NullInteger``) use ``None`` as their
null value, so e.g. ``rg_track_gain`` is None until ReplayGain runs.
Comparing that against the range bounds used to raise a TypeError.
"""
item = Item(rg_track_gain=None)

assert NumericQuery("rg_track_gain", pattern).match(item) is False


class TestPathQuery:
"""Tests for path-based querying functionality in the database system.
Expand Down
Loading