diff --git a/api/src/controllers/seed/movies.ts b/api/src/controllers/seed/movies.ts index cf46f8a2..2ddde9d0 100644 --- a/api/src/controllers/seed/movies.ts +++ b/api/src/controllers/seed/movies.ts @@ -10,9 +10,10 @@ import { import type { SeedMovie } from "~/models/movie"; import { processOptImage } from "./images"; import { guessNextRefresh } from "./refresh"; -import { inArray, sql } from "drizzle-orm"; +import { eq, getTableColumns, inArray, sql } from "drizzle-orm"; import { t } from "elysia"; import { Resource } from "~/models/utils"; +import { conflictUpdateAllExcept } from "~/db/schema/utils"; type Show = typeof shows.$inferInsert; type ShowTrans = typeof showTranslations.$inferInsert; @@ -41,12 +42,9 @@ export const seedMovie = async ( .values(movie) .onConflictDoUpdate({ target: shows.slug, - // we actually don't want to update anything, but we want to return the existing row. - // using a conflict update with a where false locks the database and ensure we don't have race conditions. - // it WONT work if we use triggers or need to handle conflicts on multiples collumns - // see https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql for more - set: { id: sql`excluded.id` }, - setWhere: sql`false`, + set: conflictUpdateAllExcept(shows, ["pk", "id", "slug", "createdAt"]), + // if year is different, this is not an update but a conflict (ex: dune-1984 vs dune-2021) + setWhere: sql`date_part('year', ${shows.startAir}) = date_part('year', excluded."start_air")`, }) .returning({ pk: shows.pk, @@ -54,11 +52,18 @@ export const seedMovie = async ( slug: shows.slug, startAir: shows.startAir, // https://stackoverflow.com/questions/39058213/differentiate-inserted-and-updated-rows-in-upsert-using-system-columns/39204667#39204667 - conflict: sql`xmax = 0`.as("conflict"), + updated: sql`(xmax = 0)`.as("updated"), + xmin: sql`xmin`, + xmax: sql`xmax`, + created: shows.createdAt, }); - if (ret.conflict) { + // TODO: the `updated` bool is always false :c + console.log(`slug: ${ret.slug}, updated: ${ret.updated}`); + console.log(ret) + if (ret.updated) { + console.log("Updated!"); if (getYear(ret.startAir) === getYear(movie.startAir)) { - return + return; } } diff --git a/api/src/db/schema/utils.ts b/api/src/db/schema/utils.ts index f2de152c..9ba146da 100644 --- a/api/src/db/schema/utils.ts +++ b/api/src/db/schema/utils.ts @@ -5,6 +5,9 @@ import { Table, View, ViewBaseConfig, + getTableColumns, + sql, + SQL, } from "drizzle-orm"; import type { AnyMySqlSelect } from "drizzle-orm/mysql-core"; import { @@ -15,6 +18,8 @@ import { } from "drizzle-orm/pg-core"; import type { AnySQLiteSelect } from "drizzle-orm/sqlite-core"; import type { WithSubquery } from "drizzle-orm/subquery"; +import { db } from ".."; +import { CasingCache } from "drizzle-orm/casing"; export const schema = pgSchema("kyoo"); @@ -24,11 +29,13 @@ export const image = () => jsonb().$type<{ id: string; source: string; blurhash: string }>(); // https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts#L58 -type Simplify = {[KeyType in keyof T]: T[KeyType]} & {}; +type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; // See https://github.com/drizzle-team/drizzle-orm/pull/1789 type Select = AnyPgSelect | AnyMySqlSelect | AnySQLiteSelect; -type AnySelect = Simplify & Partial>>; +type AnySelect = Simplify< + Omit & Partial> +>; export function getColumns< T extends | Table @@ -49,3 +56,24 @@ export function getColumns< ? (table as any)[ViewBaseConfig].selectedFields : table._.selectedFields; } + +// See https://github.com/drizzle-team/drizzle-orm/issues/1728 +export function conflictUpdateAllExcept< + T extends Table, + E extends (keyof T["_"]["columns"])[], +>(table: T, except: E) { + const columns = getTableColumns(table); + const updateColumns = Object.entries(columns).filter( + ([col]) => !except.includes(col), + ); + + return updateColumns.reduce( + (acc, [colName, col]) => { + // @ts-ignore: drizzle internal + const name = (db.dialect.casing as CasingCache).getColumnCasing(col); + acc[colName as keyof typeof acc] = sql.raw(`excluded."${name}"`); + return acc; + }, + {} as Omit, E[number]>, + ); +}