diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index da621a767..0dbd241f7 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -985,7 +985,7 @@ class FieldSort(Sort): def __init__( self, - field, + field: str, ascending: bool = True, case_insensitive: bool = True, ): @@ -999,7 +999,14 @@ class FieldSort(Sort): # attributes with different types without falling over. def key(obj: Model) -> Any: - field_val = obj.get(self.field, "") + field_val = obj.get(self.field, None) + # If the field is typed, use its null value. + if field_val is None: + if self.field in obj._types: + field_val = obj._types[self.field].null + # If not, or the null value is None, fall back to using an empty string. + if field_val is None: + field_val = '' if self.case_insensitive and isinstance(field_val, str): field_val = field_val.lower() return field_val diff --git a/test/test_sort.py b/test/test_sort.py index 1fc8a0463..cff4c9648 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -16,7 +16,8 @@ import beets.library from beets import config, dbcore -from beets.library import Album +from beets.dbcore import types +from beets.library import Album, Item from beets.test import _common from beets.test.helper import BeetsTestCase @@ -471,6 +472,10 @@ class CaseSensitivityTest(DummyDataTestCase, BeetsTestCase): class NonExistingFieldTest(DummyDataTestCase): """Test sorting by non-existing fields""" + def tearDown(self): + super().tearDown() + Item._types = {} + def test_non_existing_fields_not_fail(self): qs = ["foo+", "foo-", "--", "-+", "+-", "++", "-foo-", "-foo+", "---"] @@ -499,7 +504,7 @@ class NonExistingFieldTest(DummyDataTestCase): def test_field_present_in_some_items(self): """Test ordering by a field not present on all items.""" - # append 'foo' to two to items (1,2) + # append 'foo' to two items (1,2) items = self.lib.items("id+") ids = [i.id for i in items] items[1].foo = "bar1" @@ -514,6 +519,24 @@ class NonExistingFieldTest(DummyDataTestCase): # items without field last assert [i.id for i in results_desc] == [ids[2], ids[1], ids[0], ids[3]] + def test_int_field_present_in_some_items(self): + """Test ordering by a field not present on all items.""" + # append int-valued 'foo' to two items (1,2) + Item._types = {"foo": types.Integer()} + items = self.lib.items("id+") + ids = [i.id for i in items] + items[1].foo = 1 + items[2].foo = 2 + items[1].store() + items[2].store() + + results_asc = list(self.lib.items("foo+ id+")) + # items without field first + assert [i.id for i in results_asc] == [ids[0], ids[3], ids[1], ids[2]] + results_desc = list(self.lib.items("foo- id+")) + # items without field last + assert [i.id for i in results_desc] == [ids[2], ids[1], ids[0], ids[3]] + def test_negation_interaction(self): """Test the handling of negation and sorting together.