From 5e4c5640a1cd262fb074d0551d6f03f39dba3514 Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 24 Sep 2026 06:07:31 +0200 Subject: [PATCH] =?UTF-8?q?fix(server):=20plan=204=20=E2=80=94=20trek=5Fph?= =?UTF-8?q?oto=5Fcache=5Fmeta.cache=5Fkey=20NOT=20NULL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The column was a bare TEXT PRIMARY KEY, which SQLite does not implicitly make NOT NULL (unlike an INTEGER PRIMARY KEY rowid alias). Every writer (TrekPhotoCacheMetaRepository.upsertMeta, the table's only INSERT path) has always bound a real string — 3e's R5 confirmed this. Appends a new migration rebuilding the table with cache_key NOT NULL (SQLite has no ALTER COLUMN, so this follows the same rebuild recipe Migration20200101020000 uses in the opposite direction) and updates the entity/generated metadata to match. Schema tightening, not a behaviour change. --- .../db/entities/TrekPhotoCacheMeta.entity.ts | 4 +- ...rek_photo_cache_meta_cache_key_not_null.ts | 41 +++++++++++++++++++ .../TrekPhotoCacheMeta.repository.ts | 8 ++-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 server/src/db/migrations/Migration20200101040200_trek_photo_cache_meta_cache_key_not_null.ts diff --git a/server/src/db/entities/TrekPhotoCacheMeta.entity.ts b/server/src/db/entities/TrekPhotoCacheMeta.entity.ts index 490493311..6907a3888 100644 --- a/server/src/db/entities/TrekPhotoCacheMeta.entity.ts +++ b/server/src/db/entities/TrekPhotoCacheMeta.entity.ts @@ -4,7 +4,7 @@ import { TrekPhotoCacheMetaRepository } from '../repositories/TrekPhotoCacheMeta export class TrekPhotoCacheMeta { [EntityRepositoryType]?: TrekPhotoCacheMetaRepository; [PrimaryKeyProp]?: 'cache_key'; - cache_key?: string | null; + cache_key!: string; content_type: string & Opt = 'image/jpeg'; fetched_at!: number; } @@ -13,7 +13,7 @@ export const TrekPhotoCacheMetaSchema = defineEntity({ class: TrekPhotoCacheMeta, repository: () => TrekPhotoCacheMetaRepository, properties: { - cache_key: p.text().primary().nullable(), + cache_key: p.text().primary(), content_type: p.text().default('image/jpeg'), fetched_at: p.integer().index('idx_trek_photo_cache_meta_fetched_at'), }, diff --git a/server/src/db/migrations/Migration20200101040200_trek_photo_cache_meta_cache_key_not_null.ts b/server/src/db/migrations/Migration20200101040200_trek_photo_cache_meta_cache_key_not_null.ts new file mode 100644 index 000000000..57d11840b --- /dev/null +++ b/server/src/db/migrations/Migration20200101040200_trek_photo_cache_meta_cache_key_not_null.ts @@ -0,0 +1,41 @@ +import { Migration } from '@mikro-orm/migrations'; + +/** + * Plan 4 Task 8a program carry — `trek_photo_cache_meta.cache_key` was + * declared a bare `TEXT PRIMARY KEY` (Migration20200101014900), which SQLite + * does NOT implicitly make `NOT NULL` the way it does for an `INTEGER + * PRIMARY KEY` rowid alias — a genuine, if surprising, SQLite quirk (3e's R5 + * Task 0 finding). Every statement that writes this column + * (`TrekPhotoCacheMetaRepository.upsertMeta`'s `INSERT OR REPLACE`, its only + * writer) has always bound a real `string`, never `null` — this migration + * tightens the schema to match the code's actual invariant, not a behaviour + * change. SQLite has no `ALTER COLUMN ... SET NOT NULL`, so the column is + * added via the standard rebuild-table recipe (the same shape + * Migration20200101020000 uses in the opposite direction). + */ +export class Migration20200101040200_trek_photo_cache_meta_cache_key_not_null extends Migration { + override name = 'Migration20200101040200_trek_photo_cache_meta_cache_key_not_null'; + + override up(): void { + this.addSql(` + CREATE TABLE trek_photo_cache_meta_new ( + cache_key TEXT PRIMARY KEY NOT NULL, + content_type TEXT NOT NULL DEFAULT 'image/jpeg', + fetched_at INTEGER NOT NULL + ) + `); + + this.addSql(` + INSERT INTO trek_photo_cache_meta_new + SELECT cache_key, content_type, fetched_at + FROM trek_photo_cache_meta + WHERE cache_key IS NOT NULL + `); + + this.addSql(`DROP TABLE trek_photo_cache_meta`); + + this.addSql(`ALTER TABLE trek_photo_cache_meta_new RENAME TO trek_photo_cache_meta`); + + this.addSql(`CREATE INDEX IF NOT EXISTS idx_trek_photo_cache_meta_fetched_at ON trek_photo_cache_meta (fetched_at)`); + } +} diff --git a/server/src/db/repositories/TrekPhotoCacheMeta.repository.ts b/server/src/db/repositories/TrekPhotoCacheMeta.repository.ts index 02395e1d1..6dfbb54e7 100644 --- a/server/src/db/repositories/TrekPhotoCacheMeta.repository.ts +++ b/server/src/db/repositories/TrekPhotoCacheMeta.repository.ts @@ -26,9 +26,11 @@ export class TrekPhotoCacheMetaRepository extends TrekRepository { await this.upsert({ cache_key, content_type, fetched_at }, { onConflictFields: ['cache_key'], onConflictAction: 'merge' });