Improve library performance (#1389)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 19 Jun 2024 17:21:04 +0000 (19:21 +0200)
committerGitHub <noreply@github.com>
Wed, 19 Jun 2024 17:21:04 +0000 (19:21 +0200)
music_assistant/constants.py
music_assistant/server/controllers/media/albums.py
music_assistant/server/controllers/media/artists.py
music_assistant/server/controllers/media/base.py
music_assistant/server/controllers/media/tracks.py
music_assistant/server/controllers/music.py
music_assistant/server/helpers/database.py
music_assistant/server/providers/builtin/__init__.py
music_assistant/server/providers/filesystem_local/base.py

index 83feaebf69eb0b80afdc9bc422d4af32468a3537..fbb224c387c8359b76f82b3e1a262b91b54f34b7 100644 (file)
@@ -5,7 +5,7 @@ from typing import Final
 
 API_SCHEMA_VERSION: Final[int] = 24
 MIN_SCHEMA_VERSION: Final[int] = 24
-DB_SCHEMA_VERSION: Final[int] = 1
+DB_SCHEMA_VERSION: Final[int] = 2
 
 MASS_LOGGER_NAME: Final[str] = "music_assistant"
 
index 2bf851f524ce0583de0b9a311615980fb725f416..7ba29a0a99bdcdb2585f43ecd31683fb899cde4b 100644 (file)
@@ -30,6 +30,7 @@ from music_assistant.constants import (
     DB_TABLE_ALBUM_TRACKS,
     DB_TABLE_ALBUMS,
     DB_TABLE_ARTISTS,
+    DB_TABLE_PROVIDER_MAPPINGS,
 )
 from music_assistant.server.controllers.media.base import MediaControllerBase
 from music_assistant.server.helpers.compare import (
@@ -53,11 +54,11 @@ class AlbumsController(MediaControllerBase[Album]):
         """Initialize class."""
         super().__init__(*args, **kwargs)
         self.base_query = f"""
-        SELECT DISTINCT
-            {self.db_table}.*
-        FROM {self.db_table}
+        SELECT DISTINCT {self.db_table}.* FROM {self.db_table}
         LEFT JOIN {DB_TABLE_ALBUM_ARTISTS} on {DB_TABLE_ALBUM_ARTISTS}.album_id = {self.db_table}.item_id
         LEFT JOIN {DB_TABLE_ARTISTS} on {DB_TABLE_ARTISTS}.item_id = {DB_TABLE_ALBUM_ARTISTS}.artist_id
+        LEFT JOIN {DB_TABLE_PROVIDER_MAPPINGS} ON
+            {DB_TABLE_PROVIDER_MAPPINGS}.item_id = {self.db_table}.item_id AND media_type = '{self.media_type}'
         """  # noqa: E501
         # register (extra) api handlers
         api_base = self.api_base
index e6c1dec80d13f45b3b1622d5ddc09197603ac8ef..310b9a1190e484f662b6658a0ef43c9cdcd65f13 100644 (file)
@@ -25,10 +25,8 @@ from music_assistant.common.models.media_items import (
 )
 from music_assistant.constants import (
     DB_TABLE_ALBUM_ARTISTS,
-    DB_TABLE_ALBUMS,
     DB_TABLE_ARTISTS,
     DB_TABLE_TRACK_ARTISTS,
-    DB_TABLE_TRACKS,
     VARIOUS_ARTISTS_ID_MBID,
     VARIOUS_ARTISTS_NAME,
 )
@@ -59,15 +57,17 @@ class ArtistsController(MediaControllerBase[Artist]):
         self, favorite_only: bool = False, album_artists_only: bool = False
     ) -> int:
         """Return the total number of items in the library."""
-        sql_query = self.base_query
+        sql_query = f"SELECT item_id FROM {self.db_table}"
+        query_parts: list[str] = []
         if favorite_only:
-            sql_query += f" WHERE {self.db_table}.favorite = 1"
+            query_parts.append("favorite = 1")
         if album_artists_only:
-            sql_query += " WHERE " if "WHERE" not in sql_query else " AND "
-            sql_query += (
-                f"artists.item_id in (select {DB_TABLE_ALBUM_ARTISTS}.artist_id "
-                f"from {DB_TABLE_ALBUM_ARTISTS})"
+            query_parts.append(
+                f"item_id in (select {DB_TABLE_ALBUM_ARTISTS}.artist_id "
+                f"FROM {DB_TABLE_ALBUM_ARTISTS})"
             )
+        if query_parts:
+            sql_query += f" WHERE {' AND '.join(query_parts)}"
         return await self.mass.music.database.get_count_from_query(sql_query)
 
     async def library_items(
@@ -223,14 +223,10 @@ class ArtistsController(MediaControllerBase[Artist]):
                 item_id,
                 provider_instance_id_or_domain,
             ):
-                subquery = (
-                    "SELECT item_id FROM provider_mappings WHERE "
-                    "media_type = 'track' AND (provider_domain = :prov_id "
-                    "OR provider_instance = :prov_id)"
-                )
                 query = (
-                    f"WHERE {DB_TABLE_TRACKS}.item_id IN ({subquery}) "
-                    f"AND {DB_TABLE_TRACK_ARTISTS}.artist_id = :artist_id"
+                    f"WHERE {DB_TABLE_TRACK_ARTISTS}.artist_id = :artist_id "
+                    "AND (provider_domain = :prov_id "
+                    "OR provider_instance = :prov_id)"
                 )
                 query_params = {
                     "artist_id": db_artist.item_id,
@@ -281,14 +277,10 @@ class ArtistsController(MediaControllerBase[Artist]):
                 item_id,
                 provider_instance_id_or_domain,
             ):
-                subquery = (
-                    "SELECT item_id FROM provider_mappings WHERE "
-                    "media_type = 'album' AND (provider_domain = :prov_id "
-                    "OR provider_instance = :prov_id)"
-                )
                 query = (
-                    f"WHERE {DB_TABLE_ALBUMS}.item_id IN ({subquery}) "
-                    f"AND {DB_TABLE_ALBUM_ARTISTS}.artist_id = :artist_id"
+                    f"WHERE {DB_TABLE_ALBUM_ARTISTS}.artist_id = :artist_id "
+                    "AND (provider_domain = :prov_id "
+                    "OR provider_instance = :prov_id)"
                 )
                 query_params = {
                     "prov_id": provider_instance_id_or_domain,
index 5cfcc14e22c6150b2676edbad925e4f3b196a603..1713f5c756ffea4890e3566200c17c31ceb5b47c 100644 (file)
@@ -22,7 +22,6 @@ from music_assistant.common.models.media_items import (
     media_from_dict,
 )
 from music_assistant.constants import (
-    DB_TABLE_ALBUMS,
     DB_TABLE_ARTISTS,
     DB_TABLE_PLAYLOG,
     DB_TABLE_PROVIDER_MAPPINGS,
@@ -43,8 +42,8 @@ JSON_KEYS = ("artists", "album", "metadata", "provider_mappings", "external_ids"
 SORT_KEYS = {
     "name": "name COLLATE NOCASE ASC",
     "name_desc": "name COLLATE NOCASE DESC",
-    "sort_name": "sort_name ASC",
-    "sort_name_desc": "sort_name DESC",
+    "sort_name": "sort_name COLLATE NOCASE ASC",
+    "sort_name_desc": "sort_name COLLATE NOCASE DESC",
     "timestamp_added": "timestamp_added ASC",
     "timestamp_added_desc": "timestamp_added DESC",
     "timestamp_modified": "timestamp_modified ASC",
@@ -53,16 +52,13 @@ SORT_KEYS = {
     "last_played_desc": "last_played DESC",
     "play_count": "play_count ASC",
     "play_count_desc": "play_count DESC",
-    "artist": "artists.name COLLATE NOCASE ASC",
-    "album": "albums.name COLLATE NOCASE ASC",
-    "sort_artist": "artists.sort_name ASC",
-    "sort_album": "albums.sort_name ASC",
     "year": "year ASC",
     "year_desc": "year DESC",
     "position": "position ASC",
     "position_desc": "position DESC",
     "random": "RANDOM()",
-    "random_play_count": "RANDOM(), play_count",
+    "random_play_count": "random(), play_count ASC",
+    "random_fast": "play_count ASC",  # this one is handled with a special query
 }
 
 
@@ -76,7 +72,12 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
     def __init__(self, mass: MusicAssistant) -> None:
         """Initialize class."""
         self.mass = mass
-        self.base_query = f"SELECT * FROM {self.db_table}"
+        self.base_query = (
+            f"SELECT DISTINCT {self.db_table}.* FROM {self.db_table} "
+            f"LEFT JOIN {DB_TABLE_PROVIDER_MAPPINGS} ON "
+            f"{DB_TABLE_PROVIDER_MAPPINGS}.item_id = {self.db_table}.item_id "
+            f"AND media_type = '{self.media_type}'"
+        )
         self.logger = logging.getLogger(f"{MASS_LOGGER_NAME}.music.{self.media_type.value}")
         # register (base) api handlers
         self.api_base = api_base = f"{self.media_type}s"
@@ -193,10 +194,10 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
 
     async def library_count(self, favorite_only: bool = False) -> int:
         """Return the total number of items in the library."""
-        sql_query = self.base_query
         if favorite_only:
-            sql_query += f" WHERE {self.db_table}.favorite = 1"
-        return await self.mass.music.database.get_count_from_query(sql_query)
+            sql_query = f"SELECT item_id FROM {self.db_table} WHERE favorite = 1"
+            return await self.mass.music.database.get_count_from_query(sql_query)
+        return await self.mass.music.database.get_count(self.db_table)
 
     async def library_items(
         self,
@@ -209,6 +210,12 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         extra_query_params: dict[str, Any] | None = None,
     ) -> list[ItemCls]:
         """Get in-database items."""
+        # create special performant random query
+        if order_by == "random_fast" and not extra_query:
+            extra_query = (
+                f"{self.db_table}.rowid > (ABS(RANDOM()) % "
+                f"(SELECT max({self.db_table}.rowid) FROM {self.db_table}))"
+            )
         return await self._get_library_items_by_query(
             favorite=favorite,
             search=search,
@@ -478,25 +485,21 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         assert provider_instance_id_or_domain != "library"
         assert provider_domain != "library"
         assert provider_instance != "library"
-        subquery = f"WHERE provider_mappings.media_type = '{self.media_type.value}' "
         if provider_instance:
             query_params = {"prov_id": provider_instance}
-            subquery += "AND provider_mappings.provider_instance = :prov_id"
+            query = "provider_mappings.provider_instance = :prov_id"
         elif provider_domain:
             query_params = {"prov_id": provider_domain}
-            subquery += "AND provider_mappings.provider_domain = :prov_id"
+            query = "provider_mappings.provider_domain = :prov_id"
         else:
             query_params = {"prov_id": provider_instance_id_or_domain}
-            subquery += (
-                "AND (provider_mappings.provider_instance = :prov_id "
-                "OR provider_mappings.provider_domain = :prov_id) "
+            query = (
+                "(provider_mappings.provider_instance = :prov_id "
+                "OR provider_mappings.provider_domain = :prov_id)"
             )
         if provider_item_id:
-            subquery += " AND provider_mappings.provider_item_id = :item_id"
+            query += " AND provider_mappings.provider_item_id = :item_id"
             query_params["item_id"] = provider_item_id
-        query = (
-            f"WHERE {self.db_table}.item_id in (SELECT item_id FROM provider_mappings {subquery})"
-        )
         return await self._get_library_items_by_query(
             limit=limit, offset=offset, extra_query=query, extra_query_params=query_params
         )
@@ -751,32 +754,30 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         sql_query = self.base_query
         query_params = extra_query_params or {}
         query_parts: list[str] = []
-        # handle extra/custom query
-        if extra_query:
-            # prevent duplicate where statement
-            if extra_query.lower().startswith("where "):
-                extra_query = extra_query[5:]
-            query_parts.append(extra_query)
         # handle basic search on name
         if search:
-            query_params["search"] = f"%{search}%"
-            if self.media_type == MediaType.ALBUM:
+            # handle combined artist + title search
+            if self.media_type in (MediaType.ALBUM, MediaType.TRACK) and " - " in search:
+                artist_str, title_str = search.split(" - ", 1)
                 query_parts.append(
-                    f"({self.db_table}.name LIKE :search "
-                    f"OR {DB_TABLE_ARTISTS}.name LIKE :search)"
-                )
-            elif self.media_type == MediaType.TRACK:
-                query_parts.append(
-                    f"({self.db_table}.name LIKE :search "
-                    f"OR {DB_TABLE_ARTISTS}.name LIKE :search "
-                    f"OR {DB_TABLE_ALBUMS}.name LIKE :search)"
+                    f"({self.db_table}.name LIKE :search_title "
+                    f"AND {DB_TABLE_ARTISTS}.name LIKE :search_artist)"
                 )
+                query_params["search_title"] = f"%{title_str}%"
+                query_params["search_artist"] = f"%{artist_str}%"
             else:
+                query_params["search"] = f"%{search}%"
                 query_parts.append(f"{self.db_table}.name LIKE :search")
         # handle favorite filter
         if favorite is not None:
             query_parts.append(f"{self.db_table}.favorite = :favorite")
             query_params["favorite"] = favorite
+        # handle extra/custom query
+        if extra_query:
+            # prevent duplicate where statement
+            if extra_query.lower().startswith("where "):
+                extra_query = extra_query[5:]
+            query_parts.append(extra_query)
         # concetenate all where queries
         if query_parts:
             sql_query += " WHERE " + " AND ".join(query_parts)
@@ -784,9 +785,6 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         if order_by:
             if sort_key := SORT_KEYS.get(order_by):
                 sql_query += f" ORDER BY {sort_key}"
-            else:
-                self.logger.warning("%s is not a valid sort option!", order_by)
-
         # return dbresult parsed to media item model
         return [
             self.item_cls.from_dict(self._parse_db_row(db_row))
index 2850dc8d3bc9d33f96aad207df582f7322bfc8c2..33f91a43b4a120f9b5a88cc1cc0e3ee95c80db1b 100644 (file)
@@ -19,6 +19,7 @@ from music_assistant.constants import (
     DB_TABLE_ALBUM_TRACKS,
     DB_TABLE_ALBUMS,
     DB_TABLE_ARTISTS,
+    DB_TABLE_PROVIDER_MAPPINGS,
     DB_TABLE_TRACK_ARTISTS,
     DB_TABLE_TRACKS,
 )
@@ -60,6 +61,8 @@ class TracksController(MediaControllerBase[Track]):
         LEFT JOIN {DB_TABLE_ALBUMS} on {DB_TABLE_ALBUMS}.item_id = {DB_TABLE_ALBUM_TRACKS}.album_id
         LEFT JOIN {DB_TABLE_TRACK_ARTISTS} on {DB_TABLE_TRACK_ARTISTS}.track_id = {self.db_table}.item_id
         LEFT JOIN {DB_TABLE_ARTISTS} on {DB_TABLE_ARTISTS}.item_id = {DB_TABLE_TRACK_ARTISTS}.artist_id
+        LEFT JOIN {DB_TABLE_PROVIDER_MAPPINGS} ON
+            {DB_TABLE_PROVIDER_MAPPINGS}.item_id = {self.db_table}.item_id AND media_type = '{self.media_type}'
         """  # noqa: E501
         # register (extra) api handlers
         api_base = self.api_base
index 07db9324b1ab89b3c0dbb94c64ea81746f7276b9..295ef5250d113cef3319f8ce458fa3a84507e6bd 100644 (file)
@@ -175,7 +175,7 @@ class MusicController(CoreController):
         self,
         search_query: str,
         media_types: list[MediaType] = MediaType.ALL,
-        limit: int = 50,
+        limit: int = 25,
     ) -> SearchResults:
         """Perform global search for media items on all providers.
 
@@ -356,7 +356,7 @@ class MusicController(CoreController):
     ) -> list[MediaItemType]:
         """Return a list of the last played items."""
         if media_types is None:
-            media_types = [MediaType.TRACK, MediaType.RADIO]
+            media_types = MediaType.ALL
         media_types_str = "(" + ",".join(f'"{x}"' for x in media_types) + ")"
         query = (
             f"SELECT * FROM {DB_TABLE_PLAYLOG} WHERE media_type "
@@ -370,7 +370,13 @@ class MusicController(CoreController):
             with suppress(MediaNotFoundError, ProviderUnavailableError):
                 media_type = MediaType(db_row["media_type"])
                 ctrl = self.get_controller(media_type)
-                item = await ctrl.get_provider_item(db_row["item_id"], db_row["provider"])
+                item = await ctrl.get(
+                    db_row["item_id"],
+                    db_row["provider"],
+                    add_to_library=False,
+                    lazy=True,
+                    force_refresh=False,
+                )
                 result.append(item)
         return result
 
@@ -590,16 +596,17 @@ class MusicController(CoreController):
         """Mark item as played in playlog."""
         timestamp = utc_timestamp()
 
+        if provider_instance_id_or_domain == "builtin":
+            # we deliberately skip builtin provider items as those are often
+            # one-off items like TTS or some sound effect etc.
+            return
+
         if provider_instance_id_or_domain == "library":
             prov_key = "library"
         elif prov := self.mass.get_provider(provider_instance_id_or_domain):
             prov_key = prov.lookup_key
         else:
             prov_key = provider_instance_id_or_domain
-            # do not try to store dynamic urls (e.g. with auth token etc.),
-            # stick with plain uri/urls only
-            if "http" in item_id and "?" in item_id:
-                return
 
         # update generic playlog table
         await self.database.insert(
@@ -892,6 +899,20 @@ class MusicController(CoreController):
 
     async def __migrate_database(self, prev_version: int) -> None:
         """Perform a database migration."""
+        self.logger.info(
+            "Migrating database from version %s to %s", prev_version, DB_SCHEMA_VERSION
+        )
+        if prev_version == 1:
+            # migrate from version 1 to 2
+            await self.database.execute(
+                f"DELETE FROM {DB_TABLE_PLAYLOG} WHERE provider = 'builtin'"
+            )
+            await self.database.commit()
+            return
+
+        # all other versions: reset the database
+        # we only migrate from prtev version to current we do not try to handle
+        # more complex migrations
         self.logger.warning(
             "Database schema too old - Resetting library/database - "
             "a full rescan will be performed, this can take a while!"
@@ -1100,19 +1121,40 @@ class MusicController(CoreController):
             await self.database.execute(
                 f"CREATE INDEX IF NOT EXISTS {db_table}_name_idx on {db_table}(name);"
             )
+            # index on name (without case sensitivity)
+            await self.database.execute(
+                f"CREATE INDEX IF NOT EXISTS {db_table}_name_nocase_idx "
+                f"ON {db_table}(name COLLATE NOCASE);"
+            )
             # index on sort_name
             await self.database.execute(
                 f"CREATE INDEX IF NOT EXISTS {db_table}_sort_name_idx on {db_table}(sort_name);"
             )
+            # index on sort_name (without case sensitivity)
+            await self.database.execute(
+                f"CREATE INDEX IF NOT EXISTS {db_table}_sort_name_nocase_idx "
+                f"ON {db_table}(sort_name COLLATE NOCASE);"
+            )
             # index on external_ids
             await self.database.execute(
-                f"CREATE INDEX IF NOT EXISTS {db_table}_external_ids_idx on {db_table}(external_ids);"  # noqa: E501
+                f"CREATE INDEX IF NOT EXISTS {db_table}_external_ids_idx "
+                f"ON {db_table}(external_ids);"
             )
             # index on timestamp_added
             await self.database.execute(
                 f"CREATE INDEX IF NOT EXISTS {db_table}_timestamp_added_idx "
                 f"on {db_table}(timestamp_added);"
             )
+            # index on play_count
+            await self.database.execute(
+                f"CREATE INDEX IF NOT EXISTS {db_table}_play_count_idx "
+                f"on {db_table}(play_count);"
+            )
+            # index on last_played
+            await self.database.execute(
+                f"CREATE INDEX IF NOT EXISTS {db_table}_last_played_idx "
+                f"on {db_table}(last_played);"
+            )
 
         # indexes on provider_mappings table
         await self.database.execute(
@@ -1127,6 +1169,16 @@ class MusicController(CoreController):
             f"CREATE UNIQUE INDEX IF NOT EXISTS {DB_TABLE_PROVIDER_MAPPINGS}_provider_instance_idx "
             f"on {DB_TABLE_PROVIDER_MAPPINGS}(media_type,provider_instance,provider_item_id);"
         )
+        await self.database.execute(
+            "CREATE INDEX IF NOT EXISTS "
+            f"{DB_TABLE_PROVIDER_MAPPINGS}_media_type_provider_instance_idx "
+            f"on {DB_TABLE_PROVIDER_MAPPINGS}(media_type,provider_instance);"
+        )
+        await self.database.execute(
+            "CREATE INDEX IF NOT EXISTS "
+            f"{DB_TABLE_PROVIDER_MAPPINGS}_media_type_provider_domain_idx "
+            f"on {DB_TABLE_PROVIDER_MAPPINGS}(media_type,provider_domain);"
+        )
 
         # indexes on track_artists table
         await self.database.execute(
index bccf02a1792654a5e42ac19df9aa0d23766da75b..1237506177500bec146b5f5daef7610a1b381711 100644 (file)
@@ -23,7 +23,7 @@ ENABLE_DEBUG = os.environ.get("PYTHONDEVMODE") == "1"
 
 
 @asynccontextmanager
-async def debug_query(sql_query: str):
+async def debug_query(sql_query: str, query_params: dict | None = None):
     """Time the processing time of an sql query."""
     if not ENABLE_DEBUG:
         yield
@@ -37,7 +37,10 @@ async def debug_query(sql_query: str):
     finally:
         process_time = time.time() - time_start
         if process_time > 0.5:
-            LOGGER.warning("SQL Query took %s seconds! (\n%s", process_time, sql_query)
+            # log slow queries
+            for key, value in (query_params or {}).items():
+                sql_query = sql_query.replace(f":{key}", repr(value))
+            LOGGER.warning("SQL Query took %s seconds! (\n%s\n", process_time, sql_query)
 
 
 def query_params(query: str, params: dict[str, Any] | None) -> tuple[str, dict[str, Any]]:
@@ -117,7 +120,7 @@ class DatabaseConnection:
         if limit:
             query += f" LIMIT {limit} OFFSET {offset}"
         _query, _params = query_params(query, params)
-        async with debug_query(_query):
+        async with debug_query(_query, _params):
             return await self._db.execute_fetchall(_query, _params)
 
     async def get_count_from_query(
@@ -150,14 +153,14 @@ class DatabaseConnection:
         """Search table by column."""
         sql_query = f"SELECT * FROM {table} WHERE {table}.{column} LIKE :search"
         params = {"search": f"%{search}%"}
-        async with debug_query(sql_query):
+        async with debug_query(sql_query, params):
             return await self._db.execute_fetchall(sql_query, params)
 
     async def get_row(self, table: str, match: dict[str, Any]) -> Mapping | None:
         """Get single row for given table where column matches keys/values."""
         sql_query = f"SELECT * FROM {table} WHERE "
         sql_query += " AND ".join(f"{table}.{x} = :{x}" for x in match)
-        async with debug_query(sql_query), self._db.execute(sql_query, match) as cursor:
+        async with debug_query(sql_query, match), self._db.execute(sql_query, match) as cursor:
             return await cursor.fetchone()
 
     async def insert(
index cd6e9b083319a0d523d3c9ced2ca8c6e9b6edc8a..0da09eaf30c926b3bd1cd9b3893fb51adbde9c07 100644 (file)
@@ -514,23 +514,21 @@ class BuiltinProvider(MusicProvider):
         result: list[Track] = []
         if builtin_playlist_id == ALL_FAVORITE_TRACKS:
             res = await self.mass.music.tracks.library_items(
-                favorite=True, limit=2500, order_by="random_play_count"
+                favorite=True, limit=250000, order_by="random"
             )
             for idx, item in enumerate(res, 1):
                 item.position = idx
                 result.append(item)
             return result
         if builtin_playlist_id == RANDOM_TRACKS:
-            res = await self.mass.music.tracks.library_items(
-                limit=500, order_by="random_play_count"
-            )
+            res = await self.mass.music.tracks.library_items(limit=500, order_by="random_fast")
             for idx, item in enumerate(res, 1):
                 item.position = idx
                 result.append(item)
             return result
         if builtin_playlist_id == RANDOM_ALBUM:
             for random_album in await self.mass.music.albums.library_items(
-                limit=1, order_by="random"
+                limit=1, order_by="random_fast"
             ):
                 # use the function specified in the queue controller as that
                 # already handles unwrapping an album by user preference
@@ -543,7 +541,7 @@ class BuiltinProvider(MusicProvider):
                 return result
         if builtin_playlist_id == RANDOM_ARTIST:
             for random_artist in await self.mass.music.artists.library_items(
-                limit=1, order_by="random"
+                limit=1, order_by="random_fast"
             ):
                 # use the function specified in the queue controller as that
                 # already handles unwrapping an artist by user preference
index cfbed2db112793fb04cb396281c405b7a24fe430..db2ba4d00bcc1506b8380d063279c1f239256cae 100644 (file)
@@ -174,7 +174,8 @@ class FileSystemProviderBase(MusicProvider):
         ----------
             - path: path of the directory (relative or absolute) to list contents of.
               Empty string for provider's root.
-            - recursive: If True will recursively keep unwrapping subdirectories (scandir equivalent).
+            - recursive: If True will recursively keep unwrapping
+              subdirectories (scandir equivalent).
 
         Returns:
         -------
@@ -227,62 +228,28 @@ class FileSystemProviderBase(MusicProvider):
         """Perform search on this file based musicprovider."""
         result = SearchResults()
         # searching the filesystem is slow and unreliable,
-        # instead we make some (slow) freaking queries to the db ;-)
+        # so instead we just query the db...
+        query = "provider_mappings.provider_instance = :provider_instance "
         params = {
-            "name": f"%{search_query}%",
             "provider_instance": self.instance_id,
         }
-        subquery = "WHERE "
-        # ruff: noqa: E501
         if media_types is None or MediaType.TRACK in media_types:
-            subquery = (
-                "WHERE provider_mappings.media_type = 'track' "
-                "AND provider_mappings.provider_instance = :provider_instance"
-            )
-            query = (
-                "WHERE tracks.name LIKE :name AND tracks.item_id in "
-                f"(SELECT item_id FROM provider_mappings {subquery})"
-            )
             result.tracks = await self.mass.music.tracks._get_library_items_by_query(
-                extra_query=query, extra_query_params=params
+                search=search_query, extra_query=query, extra_query_params=params, limit=limit
             )
 
         if media_types is None or MediaType.ALBUM in media_types:
-            subquery = (
-                "WHERE provider_mappings.media_type = 'album' "
-                "AND provider_mappings.provider_instance = :provider_instance"
-            )
-            query = (
-                "WHERE albums.name LIKE :name AND albums.item_id in "
-                f"(SELECT item_id FROM provider_mappings {subquery})"
-            )
             result.albums = await self.mass.music.albums._get_library_items_by_query(
-                extra_query=query, extra_query_params=params
+                search=search_query, extra_query=query, extra_query_params=params, limit=limit
             )
 
         if media_types is None or MediaType.ARTIST in media_types:
-            subquery = (
-                "WHERE provider_mappings.media_type = 'artist' "
-                "AND provider_mappings.provider_instance = :provider_instance"
-            )
-            query = (
-                "WHERE artists.name LIKE :name AND artists.item_id in "
-                f"(SELECT item_id FROM provider_mappings {subquery})"
-            )
             result.artists = await self.mass.music.artists._get_library_items_by_query(
-                extra_query=query, extra_query_params=params
+                search=search_query, extra_query=query, extra_query_params=params, limit=limit
             )
         if media_types is None or MediaType.PLAYLIST in media_types:
-            subquery = (
-                "WHERE provider_mappings.media_type = 'playlist' "
-                "AND provider_mappings.provider_instance = :provider_instance"
-            )
-            query = (
-                "WHERE playlists.name LIKE :name AND playlists.item_id in "
-                f"(SELECT item_id FROM provider_mappings {subquery})"
-            )
             result.playlists = await self.mass.music.playlists._get_library_items_by_query(
-                extra_query=query, extra_query_params=params
+                search=search_query, extra_query=query, extra_query_params=params, limit=limit
             )
         return result
 
@@ -419,8 +386,7 @@ class FileSystemProviderBase(MusicProvider):
             f"WHERE item_id not in "
             f"( select artist_id from {DB_TABLE_TRACK_ARTISTS} "
             f"UNION SELECT artist_id from {DB_TABLE_ALBUM_ARTISTS} )"
-            f"AND item_id in ( SELECT item_id from {DB_TABLE_PROVIDER_MAPPINGS} "
-            f"WHERE provider_instance = '{self.instance_id}' and media_type = 'artist' )"
+            f"AND provider_instance = '{self.instance_id}'"
         )
         for db_row in await self.mass.music.database.get_rows_from_query(
             query,