From d8e167637ec3eb025621d5b50aacca8526877872 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 17 Feb 2019 13:41:05 -0500 Subject: [PATCH] Prototype support for named (pseudo-field) queries As discussed here: https://github.com/beetbox/beets/pull/3145#pullrequestreview-204523870 This would replace the need for #3149. --- beets/dbcore/db.py | 5 +++++ beets/dbcore/queryparse.py | 38 ++++++++++++++++++++------------------ test/test_dbcore.py | 18 ++++++++++++++++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index e92cba40c..71810ead2 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -143,6 +143,11 @@ class Model(object): are subclasses of `Sort`. """ + _queries = {} + """Named queries that use a field-like `name:value` syntax but which + do not relate to any specific field. + """ + _always_dirty = False """By default, fields only become "dirty" when their value actually changes. Enabling this flag marks fields as dirty even when the new diff --git a/beets/dbcore/queryparse.py b/beets/dbcore/queryparse.py index ce88fa3bd..1cb25a8c7 100644 --- a/beets/dbcore/queryparse.py +++ b/beets/dbcore/queryparse.py @@ -119,12 +119,13 @@ def construct_query_part(model_cls, prefixes, query_part): if not query_part: return query.TrueQuery() - # Use `model_cls` to build up a map from field names to `Query` - # classes. + # Use `model_cls` to build up a map from field (or query) names to + # `Query` classes. query_classes = {} for k, t in itertools.chain(model_cls._fields.items(), model_cls._types.items()): query_classes[k] = t.query + query_classes.update(model_cls._queries) # Non-field queries. # Parse the string. key, pattern, query_class, negate = \ @@ -137,26 +138,27 @@ def construct_query_part(model_cls, prefixes, query_part): # The query type matches a specific field, but none was # specified. So we use a version of the query that matches # any field. - q = query.AnyFieldQuery(pattern, model_cls._search_fields, - query_class) - if negate: - return query.NotQuery(q) - else: - return q + out_query = query.AnyFieldQuery(pattern, model_cls._search_fields, + query_class) else: # Non-field query type. - if negate: - return query.NotQuery(query_class(pattern)) - else: - return query_class(pattern) + out_query = query_class(pattern) - # Otherwise, this must be a `FieldQuery`. Use the field name to - # construct the query object. - key = key.lower() - q = query_class(key.lower(), pattern, key in model_cls._fields) + # Field queries get constructed according to the name of the field + # they are querying. + elif issubclass(query_class, query.FieldQuery): + key = key.lower() + out_query = query_class(key.lower(), pattern, key in model_cls._fields) + + # Non-field (named) query. + else: + out_query = query_class(pattern) + + # Apply negation. if negate: - return query.NotQuery(q) - return q + return query.NotQuery(out_query) + else: + return out_query def query_from_strings(query_cls, model_cls, prefixes, query_parts): diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 89aca442b..34994e3b3 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -36,6 +36,17 @@ class TestSort(dbcore.query.FieldSort): pass +class TestQuery(dbcore.query.Query): + def __init__(self, pattern): + self.pattern = pattern + + def clause(self): + return None, () + + def match(self): + return True + + class TestModel1(dbcore.Model): _table = 'test' _flex_table = 'testflex' @@ -49,6 +60,9 @@ class TestModel1(dbcore.Model): _sorts = { 'some_sort': TestSort, } + _queries = { + 'some_query': TestQuery, + } @classmethod def _getters(cls): @@ -519,6 +533,10 @@ class QueryFromStringsTest(unittest.TestCase): q = self.qfs(['']) self.assertIsInstance(q.subqueries[0], dbcore.query.TrueQuery) + def test_parse_named_query(self): + q = self.qfs(['some_query:foo']) + self.assertIsInstance(q.subqueries[0], TestQuery) + class SortFromStringsTest(unittest.TestCase): def sfs(self, strings):