Skip to content

Commit

Permalink
Merge pull request #823 from PierreRust/sortquery
Browse files Browse the repository at this point in the history
Sorting support in beets query mecanism (dbcore)
  • Loading branch information
sampsyo committed Aug 22, 2014
2 parents 787f0e2 + 3130a6a commit eb579cf
Show file tree
Hide file tree
Showing 15 changed files with 821 additions and 63 deletions.
3 changes: 3 additions & 0 deletions beets/config_default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ list_format_item: $artist - $album - $title
list_format_album: $albumartist - $album
time_format: '%Y-%m-%d %H:%M:%S'

sort_album: smartartist+
sort_item: smartartist+

paths:
default: $albumartist/$album%aunique{}/$track $title
singleton: Non-Album/$artist/$title
Expand Down
1 change: 1 addition & 0 deletions beets/dbcore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
from .query import Query, FieldQuery, MatchQuery, AndQuery, OrQuery
from .types import Type
from .queryparse import query_from_strings
from .queryparse import sort_from_strings

# flake8: noqa
84 changes: 52 additions & 32 deletions beets/dbcore/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import beets
from beets.util.functemplate import Template
from .query import MatchQuery
from .query import MatchQuery, build_sql
from .types import BASE_TYPE


Expand Down Expand Up @@ -483,40 +483,64 @@ class Results(object):
"""An item query result set. Iterating over the collection lazily
constructs LibModel objects that reflect database rows.
"""
def __init__(self, model_class, rows, db, query=None):
def __init__(self, model_class, rows, db, query=None, sort=None):
"""Create a result set that will construct objects of type
`model_class`, which should be a subclass of `LibModel`, out of
the query result mapping in `rows`. The new objects are
associated with the database `db`. If `query` is provided, it is
used as a predicate to filter the results for a "slow query" that
cannot be evaluated by the database directly.
associated with the database `db`.
If `query` is provided, it is used as a predicate to filter the results
for a "slow query" that cannot be evaluated by the database directly.
If `sort` is provided, it is used to sort the full list of results
before returning. This means it is a "slow sort" and all objects must
be built before returning the first one.
"""
self.model_class = model_class
self.rows = rows
self.db = db
self.query = query
self.sort = sort

def __iter__(self):
"""Construct Python objects for all rows that pass the query
predicate.
"""
for row in self.rows:
# Get the flexible attributes for the object.
with self.db.transaction() as tx:
flex_rows = tx.query(
'SELECT * FROM {0} WHERE entity_id=?'.format(
self.model_class._flex_table
),
(row['id'],)
)
values = dict(row)
flex_values = dict((row['key'], row['value']) for row in flex_rows)
if self.sort:
# Slow sort. Must build the full list first.
objects = []
for row in self.rows:
obj = self._make_model(row)
# check the predicate if any
if not self.query or self.query.match(obj):
objects.append(obj)
# Now that we have the full list, we can sort it
objects = self.sort.sort(objects)
for o in objects:
yield o
else:
for row in self.rows:
obj = self._make_model(row)
# check the predicate if any
if not self.query or self.query.match(obj):
yield obj

def _make_model(self, row):
# Get the flexible attributes for the object.
with self.db.transaction() as tx:
flex_rows = tx.query(
'SELECT * FROM {0} WHERE entity_id=?'.format(
self.model_class._flex_table
),
(row['id'],)
)

cols = dict(row)
values = dict((k, v) for (k, v) in cols.items()
if not k[:4] == 'flex')
flex_values = dict((row['key'], row['value']) for row in flex_rows)

# Construct the Python object and yield it if it passes the
# predicate.
obj = self.model_class._awaken(self.db, values, flex_values)
if not self.query or self.query.match(obj):
yield obj
# Construct the Python object
obj = self.model_class._awaken(self.db, values, flex_values)
return obj

def __len__(self):
"""Get the number of matching objects.
Expand Down Expand Up @@ -739,24 +763,20 @@ def _make_attribute_table(self, flex_table):

# Querying.

def _fetch(self, model_cls, query, order_by=None):
def _fetch(self, model_cls, query, sort_order=None):
"""Fetch the objects of type `model_cls` matching the given
query. The query may be given as a string, string sequence, a
Query object, or None (to fetch everything). If provided,
`order_by` is a SQLite ORDER BY clause for sorting.
"""
where, subvals = query.clause()
`sort_order` is either a SQLite ORDER BY clause for sorting or a
Sort object.
"""

sql, subvals, query, sort = build_sql(model_cls, query, sort_order)

sql = "SELECT * FROM {0} WHERE {1}".format(
model_cls._table,
where or '1',
)
if order_by:
sql += " ORDER BY {0}".format(order_by)
with self.transaction() as tx:
rows = tx.query(sql, subvals)

return Results(model_cls, rows, self, None if where else query)
return Results(model_cls, rows, self, query, sort)

def _get(self, model_cls, id):
"""Get a Model object by its id or None if the id does not
Expand Down
Loading

0 comments on commit eb579cf

Please sign in to comment.