diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 71810ead20..24c20ef1aa 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -524,7 +524,8 @@ 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, sort=None): + def __init__(self, model_class, rows, db, flex_rows, + query=None, sort=None): """Create a result set that will construct objects of type `model_class`. @@ -544,6 +545,7 @@ def __init__(self, model_class, rows, db, query=None, sort=None): self.db = db self.query = query self.sort = sort + self.flex_rows = flex_rows # We keep a queue of rows we haven't yet consumed for # materialization. We preserve the original total number of @@ -565,6 +567,10 @@ def _get_objects(self): a `Results` object a second time should be much faster than the first. """ + + # Index flexible attributes by the item ID, so we have easier access + flex_attrs = self._get_indexed_flex_attrs() + index = 0 # Position in the materialized objects. while index < len(self._objects) or self._rows: # Are there previously-materialized objects to produce? @@ -577,7 +583,7 @@ def _get_objects(self): else: while self._rows: row = self._rows.pop(0) - obj = self._make_model(row) + obj = self._make_model(row, flex_attrs.get(row['id'], {})) # If there is a slow-query predicate, ensurer that the # object passes it. if not self.query or self.query.match(obj): @@ -599,20 +605,24 @@ def __iter__(self): # Objects are pre-sorted (i.e., by the database). return self._get_objects() - 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'],) - ) + def _get_indexed_flex_attrs(self): + """ Index flexible attributes by the entity id they belong to + """ + flex_values = dict() + for row in self.flex_rows: + if row['entity_id'] not in flex_values: + flex_values[row['entity_id']] = dict() + + flex_values[row['entity_id']][row['key']] = row['value'] + return flex_values + + def _make_model(self, row, flex_values={}): + """ Create a Model object for the given row + """ 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 obj = self.model_class._awaken(self.db, values, flex_values) @@ -899,11 +909,25 @@ def _fetch(self, model_cls, query=None, sort=None): "ORDER BY {0}".format(order_by) if order_by else '', ) + # Fetch flexible attributes for items matching the main query. + # Doing the per-item filtering in python is faster than issuing + # one query per item to sqlite. + flex_sql = (""" + SELECT * FROM {0} WHERE entity_id IN + (SELECT id FROM {1} WHERE {2}); + """.format( + model_cls._flex_table, + model_cls._table, + where or '1', + ) + ) + with self.transaction() as tx: rows = tx.query(sql, subvals) + flex_rows = tx.query(flex_sql, subvals) return Results( - model_cls, rows, self, + model_cls, rows, self, flex_rows, None if where else query, # Slow query component. sort if sort.is_slow() else None, # Slow sort component. )