From c7df22ca7f75533439860189fa7795087460d3bd Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 24 Sep 2026 06:35:36 +0200 Subject: [PATCH] =?UTF-8?q?refactor(server):=20plan=204=20=E2=80=94=20docu?= =?UTF-8?q?ment=20unreachable=20=3F=3F=20default=20arms=20after=20single-r?= =?UTF-8?q?ow=20aggregates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3f Task 7's fix wave flagged a cluster of `row?.x ?? default` arms after unqualified, ungrouped aggregate reads (MAX/COUNT/COALESCE(SUM,0)) as "same class as OauthClients:147" but left them unexamined per-site. Decided per site: document, don't delete — TypeScript still types executeTakeFirst()'s result as `| undefined` even though SQL guarantees exactly one row for these unqualified aggregates, so the optional chain can't be removed without a non-null assertion (worse, not safer). Six sites: JourneyEntries.repository.ts (maxSortOrderForDate JG42, countStatsPlaces JG67), JourneyEntryPhotos.repository.ts (maxSortOrderForEntry JG91), JourneyPhotos.repository.ts (maxSortOrder JG88/JG99), VacayEntries.repository.ts (sumFraction VC1, sumCompFraction VC2). No behavior change — comments only. --- .../db/repositories/JourneyEntries.repository.ts | 13 +++++++++++++ .../repositories/JourneyEntryPhotos.repository.ts | 5 +++++ .../src/db/repositories/JourneyPhotos.repository.ts | 5 +++++ .../src/db/repositories/VacayEntries.repository.ts | 9 +++++++++ 4 files changed, 32 insertions(+) diff --git a/server/src/db/repositories/JourneyEntries.repository.ts b/server/src/db/repositories/JourneyEntries.repository.ts index ae0a4e415..c907da2a3 100644 --- a/server/src/db/repositories/JourneyEntries.repository.ts +++ b/server/src/db/repositories/JourneyEntries.repository.ts @@ -232,6 +232,14 @@ export class JourneyEntriesRepository extends TrekRepository { .where('journey_id', '=', journeyId) .where('entry_date', '=', entryDate) .executeTakeFirst(); + // Plan 4 Task 8b: `row?.` is unreachable — an unqualified, ungrouped + // `MAX(...)` always returns exactly one row (NULL `m` when nothing + // matches, never zero rows), so `executeTakeFirst()` can't actually miss + // here. Kept for type-shape symmetry with `executeTakeFirst()`'s + // `| undefined` return type (same accepted class as `OauthClients:147` + // — see that file's own docstring for the precedent), not dead by + // mistake. `row.m` itself genuinely can be `null` (no matching rows), + // so the `?? null` is real, not defensive filler. return row?.m ?? null; } @@ -434,6 +442,11 @@ export class JourneyEntriesRepository extends TrekRepository { .select((eb) => eb.fn.countAll().as('n')) .where('jt.journey_id', '=', journeyId) .executeTakeFirst(); + // Plan 4 Task 8b: both `row?.` AND `?? 0` are unreachable here — an + // unqualified, ungrouped `COUNT(*)` always returns exactly one row, and + // `COUNT` itself never returns `NULL` (zero matches is a real `0`, not + // an absent row). Kept for type-shape symmetry with `executeTakeFirst()` + // `| undefined` return type, same accepted class as `OauthClients:147`. return row?.n ?? 0; } diff --git a/server/src/db/repositories/JourneyEntryPhotos.repository.ts b/server/src/db/repositories/JourneyEntryPhotos.repository.ts index 8fb471be4..6f44fe953 100644 --- a/server/src/db/repositories/JourneyEntryPhotos.repository.ts +++ b/server/src/db/repositories/JourneyEntryPhotos.repository.ts @@ -164,6 +164,11 @@ export class JourneyEntryPhotosRepository extends TrekRepository eb.fn.max('sort_order').as('m')) .where('entry_id', '=', entryId) .executeTakeFirst(); + // Plan 4 Task 8b: `row?.` is unreachable — an unqualified, ungrouped + // `MAX(...)` always returns exactly one row, so `executeTakeFirst()` + // can't actually miss here. Kept for type-shape symmetry with + // `executeTakeFirst()`'s `| undefined` return type, same accepted class + // as `OauthClients:147`. `row.m` itself genuinely can be `null`. return row?.m ?? null; } diff --git a/server/src/db/repositories/JourneyPhotos.repository.ts b/server/src/db/repositories/JourneyPhotos.repository.ts index f2123de09..69afc1e51 100644 --- a/server/src/db/repositories/JourneyPhotos.repository.ts +++ b/server/src/db/repositories/JourneyPhotos.repository.ts @@ -233,6 +233,11 @@ export class JourneyPhotosRepository extends TrekRepository { .select((eb) => eb.fn.max('sort_order').as('m')) .where('journey_id', '=', journeyId) .executeTakeFirst(); + // Plan 4 Task 8b: `row?.` is unreachable — an unqualified, ungrouped + // `MAX(...)` always returns exactly one row, so `executeTakeFirst()` + // can't actually miss here. Kept for type-shape symmetry with + // `executeTakeFirst()`'s `| undefined` return type, same accepted class + // as `OauthClients:147`. `row.m` itself genuinely can be `null`. return row?.m ?? null; } diff --git a/server/src/db/repositories/VacayEntries.repository.ts b/server/src/db/repositories/VacayEntries.repository.ts index d0c40cfa2..4f7ea5baf 100644 --- a/server/src/db/repositories/VacayEntries.repository.ts +++ b/server/src/db/repositories/VacayEntries.repository.ts @@ -46,6 +46,12 @@ export class VacayEntriesRepository extends TrekRepository { .where('date', '>=', start) .where('date', '<', end) .executeTakeFirst(); + // Plan 4 Task 8b: both `row?.` AND `?? 0` are unreachable here — an + // unqualified, ungrouped `COALESCE(SUM(...), 0)` always returns exactly + // one row with a non-null value (the `COALESCE` already resolves the + // "no matching rows" case to `0` at the SQL level). Kept for type-shape + // symmetry with `executeTakeFirst()`'s `| undefined` return type, same + // accepted class as `OauthClients:147`. return row?.used ?? 0; } @@ -60,6 +66,9 @@ export class VacayEntriesRepository extends TrekRepository { .where('date', '<', end) .where('kind', '=', 'comp') .executeTakeFirst(); + // Plan 4 Task 8b: same unreachable `row?.`/`?? 0` shape as {@link + // sumFraction} above — `COALESCE(SUM(...), 0)` never returns a NULL + // value or a missing row for an unqualified, ungrouped aggregate. return row?.used ?? 0; }