From 9abe3e4d4b89e27489f379b5f4966ec501f3bf6a Mon Sep 17 00:00:00 2001 From: zk Date: Sat, 23 May 2026 20:06:09 +0100 Subject: [PATCH] fix: hard pg load (#1568) * fix: hard pg load * fix: arr of andromeda --- .../server/src/lib/search/songs.test.ts | 136 ++++++++++++++++++ typescript/server/src/utils/queries/charts.ts | 10 ++ 2 files changed, 146 insertions(+) diff --git a/typescript/server/src/lib/search/songs.test.ts b/typescript/server/src/lib/search/songs.test.ts index ea284202f..fe3d09391 100644 --- a/typescript/server/src/lib/search/songs.test.ts +++ b/typescript/server/src/lib/search/songs.test.ts @@ -2,6 +2,7 @@ import { GetChartsBySongId } from "#lib/db-formats/chart"; import { SearchGlobalGameSongsAndCharts } from "#lib/search/song-charts"; import DB from "#services/pg/db"; import { ImportSeedsSubsetForTests } from "#services/pg/seeds"; +import { seedUser } from "#test-utils/pg-fixtures"; import { resolveSeedsDir, seedsJsonAvailable } from "#test-utils/seed-paths"; import { FindChartsOnPopularity } from "#utils/queries/charts"; import { sql } from "kysely"; @@ -24,6 +25,10 @@ function makeChartId(n: number): string { return `C${n.toString(16).padStart(20, "0")}`; } +function makeScoreId(n: number): string { + return `SC${n.toString(16).padStart(20, "0")}`; +} + async function countSongRows(): Promise { const { rows } = await sql<{ c: bigint }>` SELECT count(*)::bigint AS c FROM song @@ -698,6 +703,137 @@ describe("IIDX 2dxtraSet exclusion from search", () => { ).toBeNull(); }); + it("FindChartsOnPopularity with songIDs only aggregates scores for those songs", async () => { + const targetSongID = makeSongId(892); + const otherSongID = makeSongId(893); + const targetChartID = makeChartId(892); + const otherChartID = makeChartId(893); + + await DB.insertInto("song") + .values([ + { + id: targetSongID, + legacy_id: 9_000_892, + game_group: "iidx", + title: "PopFilterTarget", + artist: "X", + search_terms: [], + alt_titles: [], + fts_document: "", + data: JSON.stringify({}), + }, + { + id: otherSongID, + legacy_id: 9_000_893, + game_group: "iidx", + title: "PopFilterOther", + artist: "X", + search_terms: [], + alt_titles: [], + fts_document: "", + data: JSON.stringify({}), + }, + ]) + .execute(); + + await DB.insertInto("chart") + .values([ + { + id: targetChartID, + legacy_id: "3".repeat(40), + game: "iidx-sp", + song_id: targetSongID, + level: "10", + level_num: 10, + is_primary: true, + difficulty: "ANOTHER", + versions: [], + data: JSON.stringify({ notecount: 100 }), + }, + { + id: otherChartID, + legacy_id: "4".repeat(40), + game: "iidx-sp", + song_id: otherSongID, + level: "11", + level_num: 11, + is_primary: true, + difficulty: "ANOTHER", + versions: [], + data: JSON.stringify({ notecount: 100 }), + }, + ]) + .execute(); + + const { id: userID } = await seedUser({ username: "pop_filter_scores" }); + await DB.insertInto("score") + .values([ + { + id: makeScoreId(892), + user_id: userID, + chart_id: targetChartID, + game: "iidx-sp", + session_id: null, + import_id: null, + data: JSON.stringify({}), + derived_data: JSON.stringify({}), + judgements: JSON.stringify({}), + calculated_data: JSON.stringify({}), + meta: JSON.stringify({}), + time_achieved: new Date().toISOString(), + time_added: new Date().toISOString(), + highlight: false, + comment: null, + }, + { + id: makeScoreId(893), + user_id: userID, + chart_id: otherChartID, + game: "iidx-sp", + session_id: null, + import_id: null, + data: JSON.stringify({}), + derived_data: JSON.stringify({}), + judgements: JSON.stringify({}), + calculated_data: JSON.stringify({}), + meta: JSON.stringify({}), + time_achieved: new Date().toISOString(), + time_added: new Date().toISOString(), + highlight: false, + comment: null, + }, + { + id: makeScoreId(894), + user_id: userID, + chart_id: otherChartID, + game: "iidx-sp", + session_id: null, + import_id: null, + data: JSON.stringify({}), + derived_data: JSON.stringify({}), + judgements: JSON.stringify({}), + calculated_data: JSON.stringify({}), + meta: JSON.stringify({}), + time_achieved: new Date().toISOString(), + time_added: new Date().toISOString(), + highlight: false, + comment: null, + }, + ]) + .execute(); + + const charts = await FindChartsOnPopularity( + "iidx-sp", + { songIDs: [targetSongID], chartIDs: undefined }, + 0, + 100, + ); + + expect(charts).toHaveLength(1); + expect(charts[0]?.chartID).toBe(targetChartID); + expect(charts[0]?.__playcount).toBe(1); + }); + it("does not apply the iidx 2dxtra song rule to other game groups", async () => { const sid = makeSongId(900); diff --git a/typescript/server/src/utils/queries/charts.ts b/typescript/server/src/utils/queries/charts.ts index d30e1bfea..925389625 100644 --- a/typescript/server/src/utils/queries/charts.ts +++ b/typescript/server/src/utils/queries/charts.ts @@ -766,6 +766,7 @@ export async function FindChartsOnPopularity( } const chartIdFilter = filters?.chartIDs; + const songIdFilter = filters?.songIDs; let q = DB.with("score_counts", (db) => { let sq = db @@ -778,6 +779,15 @@ export async function FindChartsOnPopularity( sq = sq.where("score.chart_id", "in", chartIdFilter); } + // When searching by song title, only aggregate scores for matching charts. + // Without this, every search scans and groups every score row for the game + // (e.g. 3.7M rows for bms-7k on boku) before filtering to ~100 charts. + if (songIdFilter !== undefined) { + sq = sq + .innerJoin("chart as score_count_chart", "score_count_chart.id", "score.chart_id") + .where("score_count_chart.song_id", "in", songIdFilter); + } + return sq; }) .selectFrom("chart")