Skip to content

Commit d51728f

Browse files
committed
Added possibility to add indices to arbitrary models.
1 parent 52cf741 commit d51728f

File tree

4 files changed

+72
-7
lines changed

4 files changed

+72
-7
lines changed

beets/dbcore/db.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@ class Model(ABC, Generic[D]):
287287
terms.
288288
"""
289289

290+
_indices: Sequence[types.Index] = ()
291+
"""A sequence of `Index` objects that describe the indices to be
292+
created for this table.
293+
"""
294+
290295
@cached_classproperty
291296
def _types(cls) -> dict[str, types.Type]:
292297
"""Optional types for non-fixed (flexible and computed) fields."""
@@ -1031,7 +1036,9 @@ def __init__(self, path, timeout: float = 5.0):
10311036

10321037
# Set up database schema.
10331038
for model_cls in self._models:
1034-
self._make_table(model_cls._table, model_cls._fields)
1039+
self._make_table(
1040+
model_cls._table, model_cls._fields, model_cls._indices
1041+
)
10351042
self._make_attribute_table(model_cls._flex_table)
10361043

10371044
# Primitive access control: connections and transactions.
@@ -1153,20 +1160,32 @@ def load_extension(self, path: str):
11531160

11541161
# Schema setup and migration.
11551162

1156-
def _make_table(self, table: str, fields: Mapping[str, types.Type]):
1163+
def _make_table(
1164+
self,
1165+
table: str,
1166+
fields: Mapping[str, types.Type],
1167+
indices: Sequence[types.Index] = [],
1168+
):
11571169
"""Set up the schema of the database. `fields` is a mapping
11581170
from field names to `Type`s. Columns are added if necessary.
11591171
"""
1160-
# Get current schema.
1172+
# Get current schema and existing indexes
11611173
with self.transaction() as tx:
11621174
rows = tx.query("PRAGMA table_info(%s)" % table)
1163-
current_fields = {row[1] for row in rows}
1175+
current_fields = {row[1] for row in rows}
1176+
index_rows = tx.query(f"PRAGMA index_list({table})")
1177+
current_indices = {row[1] for row in index_rows}
11641178

1179+
# Skip table creation if the current schema matches the
1180+
# requested schema (and no indexes are missing).
11651181
field_names = set(fields.keys())
1166-
if current_fields.issuperset(field_names):
1167-
# Table exists and has all the required columns.
1182+
index_names = {index.name for index in indices}
1183+
if current_fields.issuperset(
1184+
field_names
1185+
) and current_indices.issuperset(index_names):
11681186
return
11691187

1188+
# Table schema handling
11701189
if not current_fields:
11711190
# No table exists.
11721191
columns = []
@@ -1189,6 +1208,17 @@ def _make_table(self, table: str, fields: Mapping[str, types.Type]):
11891208
with self.transaction() as tx:
11901209
tx.script(setup_sql)
11911210

1211+
# Index handling
1212+
with self.transaction() as tx:
1213+
for index in indices:
1214+
if index.name in current_indices:
1215+
continue
1216+
1217+
columns_str = ", ".join(index.columns)
1218+
tx.script(
1219+
f"CREATE INDEX {index.name} ON {table} ({columns_str})"
1220+
)
1221+
11921222
def _make_attribute_table(self, flex_table: str):
11931223
"""Create a table and associated index for flexible attributes
11941224
for the given entity (if they don't exist).

beets/dbcore/types.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020
import time
2121
import typing
2222
from abc import ABC
23-
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
23+
from typing import (
24+
TYPE_CHECKING,
25+
Any,
26+
Generic,
27+
NamedTuple,
28+
Sequence,
29+
TypeVar,
30+
TypedDict,
31+
cast,
32+
)
2433

2534
import beets
2635
from beets import util
@@ -454,6 +463,23 @@ def parse(self, string):
454463
return self.null
455464

456465

466+
class Index(NamedTuple):
467+
"""A database index.
468+
469+
A helper class to represent the index
470+
information in the schema.
471+
472+
a table we do not need a table
473+
"""
474+
475+
name: str
476+
columns: Sequence[str]
477+
table: str | None = None
478+
"""As a index is normally bound to a table
479+
we can omit it here.
480+
"""
481+
482+
457483
# Shared instances of common types.
458484
DEFAULT = Default()
459485
INTEGER = Integer()

beets/library/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,12 @@ class Item(LibModel):
716716
"mtime": types.DATE,
717717
"added": types.DATE,
718718
}
719+
_indices = (
720+
types.Index(
721+
name="idx_item_album_id",
722+
columns=("album_id",),
723+
),
724+
)
719725

720726
_search_fields = (
721727
"artist",

docs/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Bug fixes:
5757
:bug:`5699`
5858
* :doc:`plugins/discogs`: Beets will no longer crash if a release has been
5959
deleted, and returns a 404.
60+
* Beets now creates an index for ``album_id`` on the ``ìtems`` sqlite table.
61+
This speeds up queries that filter items by their album significantly.
62+
:bug:`5809`
6063

6164
For packagers:
6265

0 commit comments

Comments
 (0)