Optimize queries, fix isLooking tournament teams stale bug

This commit is contained in:
Kalle
2026-09-08 18:07:53 +03:00
parent 4fd3526b6f
commit 0afbb29e21
7 changed files with 198 additions and 112 deletions

View File

@@ -70,31 +70,45 @@ function withLfgJoins<QB extends SelectQueryBuilder<any, any, any>>(qb: QB) {
.leftJoin("TournamentTeamMember", (join) =>
join
.onRef("TournamentTeamMember.userId", "=", "User.id")
.on("TournamentTeamMember.isLooking", "=", 1),
.on("TournamentTeamMember.isLooking", "=", 1)
.on((eb) =>
eb.exists(
eb
.selectFrom("TournamentTeam as LookingTeam")
.innerJoin(
"Tournament as LookingTournament",
"LookingTournament.id",
"LookingTeam.tournamentId",
)
.select("LookingTeam.id")
.whereRef(
"LookingTeam.id",
"=",
"TournamentTeamMember.tournamentTeamId",
)
.where((innerEb) =>
innerEb.or([
innerEb(
sql`json_extract("LookingTournament"."settings", '$.regClosesAt')`,
"is",
null,
),
innerEb(
sql<number>`json_extract("LookingTournament"."settings", '$.regClosesAt')`,
">",
nowTimestamp,
),
]),
),
),
),
)
.leftJoin(
"TournamentTeam",
"TournamentTeam.id",
"TournamentTeamMember.tournamentTeamId",
)
.leftJoin("Tournament", (join) =>
join
.onRef("Tournament.id", "=", "TournamentTeam.tournamentId")
.on((eb) =>
eb.or([
eb(
sql`json_extract("Tournament"."settings", '$.regClosesAt')`,
"is",
null,
),
eb(
sql<number>`json_extract("Tournament"."settings", '$.regClosesAt')`,
">",
nowTimestamp,
),
]),
),
)
.leftJoin("Tournament", "Tournament.id", "TournamentTeam.tournamentId")
.leftJoin("CalendarEvent", "CalendarEvent.tournamentId", "Tournament.id")
.leftJoin(
"CalendarEventDate",

View File

@@ -715,16 +715,12 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
const rows = await baseFindQuery
.where("ScrimPost.canceledAt", "is", null)
.where(bookedStartsAt, ">=", now)
.where((eb) =>
eb.or([
eb.exists(
eb
.selectFrom("ScrimPostUser")
.select("ScrimPostUser.scrimPostId")
.whereRef("ScrimPostUser.scrimPostId", "=", "ScrimPost.id")
.where("ScrimPostUser.userId", "=", userId),
),
eb.exists(
.where("ScrimPost.id", "in", (eb) =>
eb
.selectFrom("ScrimPostUser")
.select("ScrimPostUser.scrimPostId")
.where("ScrimPostUser.userId", "=", userId)
.union(
eb
.selectFrom("ScrimPostRequest")
.innerJoin(
@@ -733,10 +729,8 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
"ScrimPostRequest.id",
)
.select("ScrimPostRequest.scrimPostId")
.whereRef("ScrimPostRequest.scrimPostId", "=", "ScrimPost.id")
.where("ScrimPostRequestUser.userId", "=", userId),
),
]),
)
.orderBy(bookedStartsAt, "asc")
.execute();

View File

@@ -163,6 +163,13 @@ export function insertBracket(args: {
.returning(["id"])
.executeTakeFirstOrThrow();
// no team can join once a bracket has started, so none is looking for members anymore
await trx
.updateTable("TournamentTeam")
.set({ isLooking: 0 })
.where("tournamentId", "=", args.tournamentId)
.execute();
if (
args.bracket.group.length === 0 ||
args.bracket.round.length === 0 ||

View File

@@ -762,7 +762,7 @@ const nameLikeExpr = (column: string, name: string) => {
return sql<boolean>`${sql.ref(column)} like ${pattern} escape '\\'`;
};
export function findResultsByUserId(
export async function findResultsByUserId(
userId: number,
{
limit,
@@ -773,95 +773,144 @@ export function findResultsByUserId(
offset?: number;
} = {},
) {
const page =
limit !== undefined
? await findResultPageKeys(userId, filters, { limit, offset })
: null;
const calendarEventResultsQuery = baseCalendarEventResultsQuery(
userId,
filters,
).select(({ eb, fn }) => [
"CalendarEvent.id as eventId",
sql<number>`null`.as("tournamentId"),
"CalendarEventResultTeam.placement",
"CalendarEvent.participantCount",
sql<Tables["TournamentResult"]["setResults"]>`null`.as("setResults"),
sql<string | null>`null`.as("div"),
sql<string | null>`null`.as("logoUrl"),
"CalendarEvent.name as eventName",
"CalendarEventResultTeam.id as teamId",
"CalendarEventResultTeam.name as teamName",
fn<number | null>("iif", ["UserResultHighlight.userId", sql`1`, sql`0`]).as(
"isHighlight",
),
sql<number | null>`null`.as("tier"),
withMaxEventStartTime(eb),
jsonArrayFrom(
eb
.selectFrom("CalendarEventResultPlayer")
.leftJoin("User", "User.id", "CalendarEventResultPlayer.userId")
.select((mateEb) => [
...commonUserSelect(mateEb),
"CalendarEventResultPlayer.name",
])
.whereRef(
"CalendarEventResultPlayer.teamId",
"=",
"CalendarEventResultTeam.id",
)
.where((mateEb) =>
mateEb.or([
mateEb("CalendarEventResultPlayer.userId", "is", null),
mateEb("CalendarEventResultPlayer.userId", "!=", userId),
]),
),
).as("mates"),
]);
)
.$if(page !== null, (qb) =>
qb.where("CalendarEventResultTeam.id", "in", page!.calendarEventTeamIds),
)
.select(({ eb, fn }) => [
"CalendarEvent.id as eventId",
sql<number>`null`.as("tournamentId"),
"CalendarEventResultTeam.placement",
"CalendarEvent.participantCount",
sql<Tables["TournamentResult"]["setResults"]>`null`.as("setResults"),
sql<string | null>`null`.as("div"),
sql<string | null>`null`.as("logoUrl"),
"CalendarEvent.name as eventName",
"CalendarEventResultTeam.id as teamId",
"CalendarEventResultTeam.name as teamName",
fn<number | null>("iif", [
"UserResultHighlight.userId",
sql`1`,
sql`0`,
]).as("isHighlight"),
sql<number | null>`null`.as("tier"),
withMaxEventStartTime(eb),
jsonArrayFrom(
eb
.selectFrom("CalendarEventResultPlayer")
.leftJoin("User", "User.id", "CalendarEventResultPlayer.userId")
.select((mateEb) => [
...commonUserSelect(mateEb),
"CalendarEventResultPlayer.name",
])
.whereRef(
"CalendarEventResultPlayer.teamId",
"=",
"CalendarEventResultTeam.id",
)
.where((mateEb) =>
mateEb.or([
mateEb("CalendarEventResultPlayer.userId", "is", null),
mateEb("CalendarEventResultPlayer.userId", "!=", userId),
]),
),
).as("mates"),
]);
const tournamentResultsQuery = baseTournamentResultsQuery(
userId,
filters,
).select(({ eb }) => [
sql<number>`null`.as("eventId"),
"TournamentResult.tournamentId",
"TournamentResult.placement",
"TournamentResult.participantCount",
"TournamentResult.setResults",
"TournamentResult.div",
tournamentLogoOrNull(eb).as("logoUrl"),
"CalendarEvent.name as eventName",
"TournamentTeam.id as teamId",
"TournamentTeam.name as teamName",
"TournamentResult.isHighlight",
RESULT_TIER.as("tier"),
withMaxEventStartTime(eb),
jsonArrayFrom(
eb
.selectFrom("TournamentResult as TournamentResult2")
.innerJoin("User", "User.id", "TournamentResult2.userId")
.select((mateEb) => [
...commonUserSelect(mateEb),
sql<string | null>`null`.as("name"),
])
.whereRef(
"TournamentResult2.tournamentTeamId",
"=",
"TournamentResult.tournamentTeamId",
)
.where("TournamentResult2.userId", "!=", userId),
).as("mates"),
]);
const tournamentResultsQuery = baseTournamentResultsQuery(userId, filters)
.$if(page !== null, (qb) =>
qb.where(
"TournamentResult.tournamentTeamId",
"in",
page!.tournamentTeamIds,
),
)
.select(({ eb }) => [
sql<number>`null`.as("eventId"),
"TournamentResult.tournamentId",
"TournamentResult.placement",
"TournamentResult.participantCount",
"TournamentResult.setResults",
"TournamentResult.div",
tournamentLogoOrNull(eb).as("logoUrl"),
"CalendarEvent.name as eventName",
"TournamentTeam.id as teamId",
"TournamentTeam.name as teamName",
"TournamentResult.isHighlight",
RESULT_TIER.as("tier"),
withMaxEventStartTime(eb),
jsonArrayFrom(
eb
.selectFrom("TournamentResult as TournamentResult2")
.innerJoin("User", "User.id", "TournamentResult2.userId")
.select((mateEb) => [
...commonUserSelect(mateEb),
sql<string | null>`null`.as("name"),
])
.whereRef(
"TournamentResult2.tournamentTeamId",
"=",
"TournamentResult.tournamentTeamId",
)
.where("TournamentResult2.userId", "!=", userId),
).as("mates"),
]);
let query = calendarEventResultsQuery
return calendarEventResultsQuery
.unionAll(tournamentResultsQuery)
.orderBy("startsAt", "desc")
.$narrowType<{ startsAt: NotNull }>();
.$narrowType<{ startsAt: NotNull }>()
.execute();
}
if (limit !== undefined) {
query = query.limit(limit);
}
/**
* Identities of the results on one page, newest first. Resolved on their own because the
* per-row columns of {@link findResultsByUserId} (mates, logos) would otherwise be computed
* for the user's every result before the sort and limit.
*/
async function findResultPageKeys(
userId: number,
filters: ResultsFilters,
{ limit, offset }: { limit: number; offset?: number },
) {
const rows = await baseCalendarEventResultsQuery(userId, filters)
.select((eb) => [
sql<number | null>`"CalendarEventResultTeam"."id"`.as(
"calendarEventTeamId",
),
sql<number | null>`null`.as("tournamentTeamId"),
withMaxEventStartTime(eb),
])
.unionAll(
baseTournamentResultsQuery(userId, filters).select((eb) => [
sql<number | null>`null`.as("calendarEventTeamId"),
sql<number | null>`"TournamentResult"."tournamentTeamId"`.as(
"tournamentTeamId",
),
withMaxEventStartTime(eb),
]),
)
.orderBy("startsAt", "desc")
.limit(limit)
.$if(offset !== undefined, (qb) => qb.offset(offset!))
.execute();
if (offset !== undefined) {
query = query.offset(offset);
}
return query.execute();
return {
calendarEventTeamIds: rows.flatMap((row) =>
row.calendarEventTeamId !== null ? [row.calendarEventTeamId] : [],
),
tournamentTeamIds: rows.flatMap((row) =>
row.tournamentTeamId !== null ? [row.tournamentTeamId] : [],
),
};
}
export async function countResultsByUserId(

View File

@@ -0,0 +1,4 @@
---
type: bug
---
Friends looking for a pickup team in a tournament with open registration show up as such in the sidebar again

View File

@@ -0,0 +1,12 @@
import { type Kysely, sql } from "kysely";
/**
* `TournamentTeam.isLooking` used to stay set after the tournament had started. Starting a
* bracket clears it from now on, so the sidebar friend activity no longer joins every past
* pickup registration.
*/
export async function up(db: Kysely<any>): Promise<void> {
await sql`update "TournamentTeam" set "isLooking" = 0 where "isLooking" = 1 and "tournamentId" in (select "tournamentId" from "TournamentStage")`.execute(
db,
);
}

View File

@@ -1368,6 +1368,12 @@ export function buildCases(fx: Fixtures): {
add("UserRepository.findResultsByUserId", fx.heavyUser, (user) =>
UserRepository.findResultsByUserId(user.id, {}),
);
add("UserRepository.findResultsByUserId.page", fx.heavyUser, (user) =>
UserRepository.findResultsByUserId(user.id, { limit: 25, offset: 25 }),
);
add("UserRepository.findResultsByUserId.widget", fx.heavyUser, (user) =>
UserRepository.findResultsByUserId(user.id, { limit: 3 }),
);
add("UserRepository.countResultsByUserId", fx.heavyUser, (user) =>
UserRepository.countResultsByUserId(user.id),
);