diff --git a/beets/library.py b/beets/library.py index 9d5219b18..b52d5f873 100644 --- a/beets/library.py +++ b/beets/library.py @@ -44,6 +44,23 @@ log = logging.getLogger('beets') # Library-specific query types. +class SingletonQuery(dbcore.FieldQuery): + """This query is responsible for the 'singleton' lookup. + + It is based on the FieldQuery and constructs a SQL clause + 'album_id is NULL' which yields the same result as the previous filter + in Python but is more performant since it's done in SQL. + + Using util.str2bool ensures that lookups like singleton:true, singleton:1 + and singleton:false, singleton:0 are handled consistently. + """ + def __new__(cls, field, value, *args, **kwargs): + query = dbcore.query.NoneQuery('album_id') + if util.str2bool(value): + return query + return dbcore.query.NotQuery(query) + + class PathQuery(dbcore.FieldQuery): """A query that matches all items under a given path. @@ -569,6 +586,8 @@ class Item(LibModel): _sorts = {'artist': SmartArtistSort} + _queries = {'singleton': SingletonQuery} + _format_config_key = 'format_item' # Cached album object. Read-only. diff --git a/docs/changelog.rst b/docs/changelog.rst index 36656c6fe..61128d732 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,8 @@ New features: * Added option to specify a URL in the `embedart` plugin. :bug:`83` +* :ref:`list-cmd` `singleton:true` queries have been made faster +* :ref:`list-cmd` `singleton:1` and `singleton:0` can now alternatively be used in queries, same as `comp` * --from-logfile now parses log files using a UTF-8 encoding in `beets/beets/ui/commands.py`. :bug:`4693` * Added additional error handling for `spotify` plugin. diff --git a/test/test_query.py b/test/test_query.py index 3d7b56781..5458676db 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -290,11 +290,21 @@ class GetTest(DummyDataTestCase): results = self.lib.items(q) self.assert_items_matched(results, ['beets 4 eva']) + def test_singleton_1(self): + q = 'singleton:1' + results = self.lib.items(q) + self.assert_items_matched(results, ['beets 4 eva']) + def test_singleton_false(self): q = 'singleton:false' results = self.lib.items(q) self.assert_items_matched(results, ['foo bar', 'baz qux']) + def test_singleton_0(self): + q = 'singleton:0' + results = self.lib.items(q) + self.assert_items_matched(results, ['foo bar', 'baz qux']) + def test_compilation_true(self): q = 'comp:true' results = self.lib.items(q)