diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index 3653434c16..4c86dd07c6 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -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: diff --git a/docs/changelog.rst b/docs/changelog.rst index 096a35a6db..e71c4f5f0f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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 diff --git a/test/dbcore/test_query.py b/test/dbcore/test_query.py index 8f2a8921ce..0d9cd82d18 100644 --- a/test/dbcore/test_query.py +++ b/test/dbcore/test_query.py @@ -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.