Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small performance optimization when fetching items from library. #2798

Merged
merged 4 commits into from
Mar 23, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions beets/dbcore/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand All @@ -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
Expand All @@ -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?
Expand All @@ -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):
Expand All @@ -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={}):
pprkut marked this conversation as resolved.
Show resolved Hide resolved
""" 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)
Expand Down Expand Up @@ -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.
)
Expand Down