diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index 6a10f2533..df6cd2ff1 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -18,6 +18,10 @@ import re from operator import attrgetter from beets import util from datetime import datetime, timedelta +from collections import namedtuple + + +SortedQuery = namedtuple('SortedQuery', 'query', 'sort') class Query(object): @@ -500,19 +504,21 @@ class DateQuery(FieldQuery): return clause, subvals +# Sorting. + class Sort(object): - """An abstract class representing a sort operation for a query into the - item database. + """An abstract class representing a sort operation for a query into + the item database. """ + def select_clause(self): - """ Generates a select sql fragment if the sort operation requires one, - an empty string otherwise. + """Generate a SELECT fragment (possibly an empty string) for the + sort. """ return "" def union_clause(self): - """ Generates a union sql fragment if the sort operation requires one, - an empty string otherwise. + """Generate a join SQL fragment (possibly an empty string). """ return "" @@ -523,47 +529,32 @@ class Sort(object): return None def sort(self, items): - """Return a key function that can be used with the list.sort() method. - Meant to be used with slow sort, it must be implemented even for sort - that can be done with sql, as they might be used in conjunction with - slow sort. + """Sort the list of objects and return a list. """ return sorted(items, key=lambda x: x) def is_slow(self): + """Indicate whether this query is *slow*, meaning that it cannot + be executed in SQL and must be executed in Python. + """ return False class MultipleSort(Sort): - """Sort class that combines several sort criteria. - This implementation tries to implement as many sort operation in sql, - falling back to python sort only when necessary. + """Sort that encapsulates multiple sub-sorts. """ def __init__(self): self.sorts = [] - def add_criteria(self, sort): + def add_sort(self, sort): self.sorts.append(sort) - def _sql_sorts(self): - """ Returns the list of sort for which sql can be used - """ - # with several Sort, we can use SQL sorting only if there is only - # SQL-capable Sort or if the list ends with SQl-capable Sort. - sql_sorts = [] - for sort in reversed(self.sorts): - if not sort.order_clause() is None: - sql_sorts.append(sort) - else: - break - sql_sorts.reverse() - return sql_sorts - def select_clause(self): - sql_sorts = self._sql_sorts() + if self.is_slow(): + return "" select_strings = [] - for sort in sql_sorts: + for sort in self.sorts: select = sort.select_clause() if select: select_strings.append(select) @@ -572,18 +563,20 @@ class MultipleSort(Sort): return select_string def union_clause(self): - sql_sorts = self._sql_sorts() + if self.is_slow(): + return "" union_strings = [] - for sort in sql_sorts: + for sort in self.sorts: union = sort.union_clause() union_strings.append(union) return "".join(union_strings) def order_clause(self): - sql_sorts = self._sql_sorts() + if self.is_slow(): + return None order_strings = [] - for sort in sql_sorts: + for sort in self.sorts: order = sort.order_clause() order_strings.append(order) @@ -621,12 +614,12 @@ class FlexFieldSort(Sort): self.is_ascending = is_ascending def select_clause(self): - """ Return a select sql fragment. + """Return a SELECT fragment. """ return "sort_flexattr{0!s}.value as flex_{0!s} ".format(self.field) def union_clause(self): - """ Returns an union sql fragment. + """Return a JOIN fragment. """ union = ("LEFT JOIN {flextable} as sort_flexattr{index!s} " "ON {table}.id = sort_flexattr{index!s}.entity_id " @@ -637,7 +630,7 @@ class FlexFieldSort(Sort): return union def order_clause(self): - """ Returns an order sql fragment. + """Return an ORDER BY fragment. """ order = "ASC" if self.is_ascending else "DESC" return "flex_{0} {1} ".format(self.field, order) diff --git a/beets/dbcore/queryparse.py b/beets/dbcore/queryparse.py index b51194b33..324274a5a 100644 --- a/beets/dbcore/queryparse.py +++ b/beets/dbcore/queryparse.py @@ -149,5 +149,5 @@ def sort_from_strings(model_cls, sort_parts): return None sort = query.MultipleSort() for part in sort_parts: - sort.add_criteria(construct_sort_part(model_cls, part)) + sort.add_sort(construct_sort_part(model_cls, part)) return sort diff --git a/test/test_sort.py b/test/test_sort.py index 76e5a35cb..1c6cc9741 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -113,8 +113,8 @@ class SortFixedFieldTest(DummyDataTestCase): s1 = dbcore.query.FixedFieldSort("album", True) s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.items(q, sort) self.assertLessEqual(results[0]['album'], results[1]['album']) self.assertLessEqual(results[1]['album'], results[2]['album']) @@ -160,8 +160,8 @@ class SortFlexFieldTest(DummyDataTestCase): s1 = dbcore.query.FlexFieldSort(beets.library.Item, "flex2", False) s2 = dbcore.query.FlexFieldSort(beets.library.Item, "flex1", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.items(q, sort) self.assertGreaterEqual(results[0]['flex2'], results[1]['flex2']) self.assertGreaterEqual(results[1]['flex2'], results[2]['flex2']) @@ -205,8 +205,8 @@ class SortAlbumFixedFieldTest(DummyDataTestCase): s1 = dbcore.query.FixedFieldSort("genre", True) s2 = dbcore.query.FixedFieldSort("album", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['genre'], results[1]['genre']) self.assertLessEqual(results[1]['genre'], results[2]['genre']) @@ -250,8 +250,8 @@ class SortAlbumFlexdFieldTest(DummyDataTestCase): s1 = dbcore.query.FlexFieldSort(beets.library.Album, "flex2", True) s2 = dbcore.query.FlexFieldSort(beets.library.Album, "flex1", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['flex2'], results[1]['flex2']) self.assertLessEqual(results[1]['flex2'], results[2]['flex2']) @@ -299,8 +299,8 @@ class SortCombinedFieldTest(DummyDataTestCase): s1 = dbcore.query.ComputedFieldSort(beets.library.Album, "path", True) s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['path'], results[1]['path']) self.assertLessEqual(results[1]['path'], results[2]['path']) @@ -314,8 +314,8 @@ class SortCombinedFieldTest(DummyDataTestCase): s1 = dbcore.query.FixedFieldSort("year", True) s2 = dbcore.query.ComputedFieldSort(beets.library.Album, "path", True) sort = dbcore.query.MultipleSort() - sort.add_criteria(s1) - sort.add_criteria(s2) + sort.add_sort(s1) + sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['year'], results[1]['year']) self.assertLessEqual(results[1]['year'], results[2]['year'])