From 01f3cfef4512365fab59698e415f0aa711d5a201 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 5 Feb 2026 02:11:08 -0800 Subject: [PATCH] db: improve genre and tag handling --- src/lib/db/import-amq.ts | 20 ++++++++++-- src/lib/db/schema/index.ts | 63 +++++++++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/lib/db/import-amq.ts b/src/lib/db/import-amq.ts index fa8c8c2..2edc95e 100644 --- a/src/lib/db/import-amq.ts +++ b/src/lib/db/import-amq.ts @@ -28,11 +28,13 @@ import { artistAltNamesTable, artistGroupsTable, artistsTable, + genresTable, groupAltNamesTable, groupArtistMembersTable, groupGroupMembersTable, groupsTable, songsTable, + tagsTable, } from "./schema"; /** @@ -428,11 +430,18 @@ export async function importAmqData( // genres if (a.genres.length) { + // Ensure lookup rows exist (string PK) + db.insert(genresTable) + .values(a.genres.map((g) => ({ name: g }))) + .onConflictDoNothing() + .run(); + + // Insert relations db.insert(animeGenresTable) .values( a.genres.map((g) => ({ annId: a.annId, - genre: g, + genreName: g, })), ) .run(); @@ -440,11 +449,18 @@ export async function importAmqData( // tags if (a.tags.length) { + // Ensure lookup rows exist (string PK) + db.insert(tagsTable) + .values(a.tags.map((t) => ({ name: t }))) + .onConflictDoNothing() + .run(); + + // Insert relations db.insert(animeTagsTable) .values( a.tags.map((t) => ({ annId: a.annId, - tag: t, + tagName: t, })), ) .run(); diff --git a/src/lib/db/schema/index.ts b/src/lib/db/schema/index.ts index 0d0d559..f4cb47f 100644 --- a/src/lib/db/schema/index.ts +++ b/src/lib/db/schema/index.ts @@ -59,9 +59,9 @@ export const animeTable = sqliteTable( insertCount: integer("insert_count").notNull(), }, (t) => ({ - aniListIndex: uniqueIndex("anime_anilist_id_uq").on(t.aniListId), - malIndex: uniqueIndex("anime_mal_id_uq").on(t.malId), - kitsuIndex: uniqueIndex("anime_kitsu_id_uq").on(t.kitsuId), + aniListIndex: index("anime_anilist_id_uq").on(t.aniListId), + malIndex: index("anime_mal_id_uq").on(t.malId), + kitsuIndex: index("anime_kitsu_id_uq").on(t.kitsuId), }), ); @@ -279,38 +279,65 @@ export const animeNamesTable = sqliteTable( }), ); +export const genresTable = sqliteTable( + "genres", + { + /** Primary key is the genre string itself */ + name: text("name").notNull().primaryKey(), + }, + (t) => ({ + nameIndex: index("genres_name_idx").on(t.name), + }), +); + export const animeGenresTable = sqliteTable( "anime_genres", { - id: integer("id").notNull().primaryKey({ autoIncrement: true }), annId: integer("ann_id") .notNull() .references(() => animeTable.annId, { onDelete: "cascade" }), - genre: text("genre").notNull(), + genreName: text("genre_name") + .notNull() + .references(() => genresTable.name, { onDelete: "cascade" }), }, (t) => ({ + pk: primaryKey({ + name: "anime_genres_pk", + columns: [t.annId, t.genreName], + }), animeIndex: index("anime_genres_ann_id_idx").on(t.annId), - genreIndex: index("anime_genres_genre_idx").on(t.genre), - uniquePerAnime: uniqueIndex("anime_genres_ann_genre_uq").on( - t.annId, - t.genre, - ), + genreIndex: index("anime_genres_genre_name_idx").on(t.genreName), + }), +); + +export const tagsTable = sqliteTable( + "tags", + { + /** Primary key is the tag string itself */ + name: text("name").notNull().primaryKey(), + }, + (t) => ({ + nameIndex: index("tags_name_idx").on(t.name), }), ); export const animeTagsTable = sqliteTable( "anime_tags", { - id: integer("id").notNull().primaryKey({ autoIncrement: true }), annId: integer("ann_id") .notNull() .references(() => animeTable.annId, { onDelete: "cascade" }), - tag: text("tag").notNull(), + tagName: text("tag_name") + .notNull() + .references(() => tagsTable.name, { onDelete: "cascade" }), }, (t) => ({ + pk: primaryKey({ + name: "anime_tags_pk", + columns: [t.annId, t.tagName], + }), animeIndex: index("anime_tags_ann_id_idx").on(t.annId), - tagIndex: index("anime_tags_tag_idx").on(t.tag), - uniquePerAnime: uniqueIndex("anime_tags_ann_tag_uq").on(t.annId, t.tag), + tagIndex: index("anime_tags_tag_name_idx").on(t.tagName), }), ); @@ -387,6 +414,10 @@ export const animeGenresRelations = relations(animeGenresTable, ({ one }) => ({ fields: [animeGenresTable.annId], references: [animeTable.annId], }), + genre: one(genresTable, { + fields: [animeGenresTable.genreName], + references: [genresTable.name], + }), })); export const animeTagsRelations = relations(animeTagsTable, ({ one }) => ({ @@ -394,6 +425,10 @@ export const animeTagsRelations = relations(animeTagsTable, ({ one }) => ({ fields: [animeTagsTable.annId], references: [animeTable.annId], }), + tag: one(tagsTable, { + fields: [animeTagsTable.tagName], + references: [tagsTable.name], + }), })); export const animeSongLinksRelations = relations(