Fix bug when using random order (#2206)
authorOzGav <gavnosp@hotmail.com>
Sun, 1 Jun 2025 11:53:31 +0000 (21:53 +1000)
committerGitHub <noreply@github.com>
Sun, 1 Jun 2025 11:53:31 +0000 (13:53 +0200)
music_assistant/controllers/media/base.py

index 723b5cf68245d1da3c7e1df6b1b23e3176bcb758..ccdea0a070545ca52289688c61387352842f1633 100644 (file)
@@ -714,22 +714,19 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         query_params = extra_query_params or {}
         query_parts: list[str] = extra_query_parts or []
         join_parts: list[str] = extra_join_parts or []
-        # create special performant random query
-        if not search and order_by and order_by.startswith("random"):
-            query_parts.append(
-                f"{self.db_table}.item_id in "
-                f"(SELECT item_id FROM {self.db_table} ORDER BY RANDOM() LIMIT {limit})"
-            )
+
         # handle search
         if search:
-            search = create_safe_string(search, True, True)
-            query_params["search"] = f"%{search}%"
+            safe_search = create_safe_string(search, True, True)
+            query_params["search"] = f"%{safe_search}%"
             query_parts.append(f"{self.db_table}.search_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 provider filter
+
+        # handle provider join
         if provider:
             join_parts.append(
                 f"JOIN provider_mappings ON provider_mappings.item_id = {self.db_table}.item_id "
@@ -737,23 +734,45 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
                 f"AND (provider_mappings.provider_instance = '{provider}' "
                 f"OR provider_mappings.provider_domain = '{provider}')"
             )
-        # prevent duplicate where statement
+
+        # Prevent duplicate WHERE
         query_parts = [x[5:] if x.lower().startswith("where ") else x for x in query_parts]
-        # concetenate all join and/or where queries
-        if join_parts:
-            sql_query += f" {' '.join(join_parts)} "
-        if query_parts:
-            sql_query += " WHERE " + " AND ".join(query_parts)
-        # build final query
-        sql_query += f" GROUP BY {self.db_table}.item_id"
-        if order_by:
-            if sort_key := SORT_KEYS.get(order_by):
+
+        # --- Optimized RANDOM ORDER block ---
+        if order_by and order_by.startswith("random"):
+            # Build filtered subquery for item IDs
+            subquery = f"SELECT {self.db_table}.item_id FROM {self.db_table}"
+            if query_parts:
+                subquery += " WHERE " + " AND ".join(query_parts)
+            subquery += f" ORDER BY RANDOM() LIMIT {limit}"
+
+            # Main query joins the subquery
+            sql_query = (
+                f"SELECT * FROM {self.db_table} "
+                f"JOIN ({subquery}) AS filtered_random "
+                f"ON {self.db_table}.item_id = filtered_random.item_id "
+                f"GROUP BY {self.db_table}.item_id"
+            )
+            # Note: limit is already applied in subquery, so use 0 offset
+            # and large limit to pass results
+            query_limit = limit
+            query_offset = 0
+        else:
+            # Normal query path
+            sql_query = self.base_query
+            if join_parts:
+                sql_query += f" {' '.join(join_parts)}"
+            if query_parts:
+                sql_query += " WHERE " + " AND ".join(query_parts)
+            sql_query += f" GROUP BY {self.db_table}.item_id"
+            if order_by and (sort_key := SORT_KEYS.get(order_by)):
                 sql_query += f" ORDER BY {sort_key}"
-        # return dbresult parsed to media item model
+            query_limit = limit
+            query_offset = offset
         return [
             self.item_cls.from_dict(self._parse_db_row(db_row))
             for db_row in await self.mass.music.database.get_rows_from_query(
-                sql_query, query_params, limit=limit, offset=offset
+                sql_query, query_params, limit=query_limit, offset=query_offset
             )
         ]