diff --git a/prisma/migrations/0009_radio_contests_giveaways_columns.sql b/prisma/migrations/0009_radio_contests_giveaways_columns.sql index 719c36b9..8567c911 100644 --- a/prisma/migrations/0009_radio_contests_giveaways_columns.sql +++ b/prisma/migrations/0009_radio_contests_giveaways_columns.sql @@ -3,27 +3,27 @@ -- columns. These tables were created in migration 0004 with only id/timestamps. ALTER TABLE `radio_contests` - ADD COLUMN `title` VARCHAR(255) NOT NULL AFTER `id`, - ADD COLUMN `description` TEXT NULL AFTER `title`, - ADD COLUMN `prize` VARCHAR(255) NULL AFTER `description`, - ADD COLUMN `start_date` TIMESTAMP(0) NULL AFTER `prize`, - ADD COLUMN `end_date` TIMESTAMP(0) NULL AFTER `start_date`, - ADD COLUMN `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`, - ADD COLUMN `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`, - ADD COLUMN `created_by` BIGINT UNSIGNED NULL AFTER `is_active`, + ADD COLUMN IF NOT EXISTS `title` VARCHAR(255) NOT NULL AFTER `id`, + ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`, + ADD COLUMN IF NOT EXISTS `prize` VARCHAR(255) NULL AFTER `description`, + ADD COLUMN IF NOT EXISTS `start_date` TIMESTAMP(0) NULL AFTER `prize`, + ADD COLUMN IF NOT EXISTS `end_date` TIMESTAMP(0) NULL AFTER `start_date`, + ADD COLUMN IF NOT EXISTS `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`, + ADD COLUMN IF NOT EXISTS `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`, + ADD COLUMN IF NOT EXISTS `created_by` BIGINT UNSIGNED NULL AFTER `is_active`, MODIFY COLUMN `created_at` TIMESTAMP(0) NULL AFTER `created_by`, MODIFY COLUMN `updated_at` TIMESTAMP(0) NULL AFTER `created_at`; ALTER TABLE `radio_giveaways` - ADD COLUMN `title` VARCHAR(255) NOT NULL AFTER `id`, - ADD COLUMN `description` TEXT NULL AFTER `title`, - ADD COLUMN `prize` VARCHAR(255) NULL AFTER `description`, - ADD COLUMN `prize_amount` INT NOT NULL DEFAULT 0 AFTER `prize`, - ADD COLUMN `prize_currency` VARCHAR(50) NULL AFTER `prize_amount`, - ADD COLUMN `start_date` TIMESTAMP(0) NULL AFTER `prize_currency`, - ADD COLUMN `end_date` TIMESTAMP(0) NULL AFTER `start_date`, - ADD COLUMN `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`, - ADD COLUMN `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`, - ADD COLUMN `created_by` BIGINT UNSIGNED NULL AFTER `is_active`, + ADD COLUMN IF NOT EXISTS `title` VARCHAR(255) NOT NULL AFTER `id`, + ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`, + ADD COLUMN IF NOT EXISTS `prize` VARCHAR(255) NULL AFTER `description`, + ADD COLUMN IF NOT EXISTS `prize_amount` INT NOT NULL DEFAULT 0 AFTER `prize`, + ADD COLUMN IF NOT EXISTS `prize_currency` VARCHAR(50) NULL AFTER `prize_amount`, + ADD COLUMN IF NOT EXISTS `start_date` TIMESTAMP(0) NULL AFTER `prize_currency`, + ADD COLUMN IF NOT EXISTS `end_date` TIMESTAMP(0) NULL AFTER `start_date`, + ADD COLUMN IF NOT EXISTS `winner_id` BIGINT UNSIGNED NULL AFTER `end_date`, + ADD COLUMN IF NOT EXISTS `is_active` BOOLEAN NOT NULL DEFAULT true AFTER `winner_id`, + ADD COLUMN IF NOT EXISTS `created_by` BIGINT UNSIGNED NULL AFTER `is_active`, MODIFY COLUMN `created_at` TIMESTAMP(0) NULL AFTER `created_by`, MODIFY COLUMN `updated_at` TIMESTAMP(0) NULL AFTER `created_at`; diff --git a/scripts/apply-migrations.ts b/scripts/apply-migrations.ts index 74199da0..f4849ea6 100644 --- a/scripts/apply-migrations.ts +++ b/scripts/apply-migrations.ts @@ -3,6 +3,7 @@ import { readFileSync, readdirSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { splitSqlStatements } from "./sql-statements"; +import { mysqlConnectionUrl } from "./db-url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const MIGRATIONS_DIR = resolve(__dirname, "../prisma/migrations"); @@ -17,8 +18,9 @@ interface MigrationFile { function getDbConfig(): { url: string; database: string } { const url = process.env.DATABASE_URL; if (!url) throw new Error("DATABASE_URL is required"); - const dbName = url.split("/").pop()?.split("?")[0] ?? "atomcms"; - return { url, database: dbName }; + const parsed = new URL(url); + const dbName = decodeURIComponent(parsed.pathname.replace(/^\//, "")) || "atomcms"; + return { url: mysqlConnectionUrl(url), database: dbName }; } async function ensureConnection(): Promise { diff --git a/scripts/db-url.test.ts b/scripts/db-url.test.ts new file mode 100644 index 00000000..7c42c31b --- /dev/null +++ b/scripts/db-url.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { mysqlConnectionUrl } from "./db-url"; + +describe("mysqlConnectionUrl", () => { + it("removes Prisma-only pool options before passing the URL to MySQL2", () => { + const result = mysqlConnectionUrl("mysql://user:pass@localhost:3306/cms?connection_limit=20&pool_timeout=30&charset=utf8mb4"); + expect(result).not.toContain("connection_limit"); + expect(result).not.toContain("pool_timeout"); + expect(result).toContain("charset=utf8mb4"); + }); +}); diff --git a/scripts/db-url.ts b/scripts/db-url.ts new file mode 100644 index 00000000..a33d59f5 --- /dev/null +++ b/scripts/db-url.ts @@ -0,0 +1,7 @@ +const MYSQL2_UNSUPPORTED_OPTIONS = ["connection_limit", "pool_timeout"] as const; + +export function mysqlConnectionUrl(value: string): string { + const url = new URL(value); + for (const option of MYSQL2_UNSUPPORTED_OPTIONS) url.searchParams.delete(option); + return url.toString(); +} diff --git a/scripts/migration-idempotency.test.ts b/scripts/migration-idempotency.test.ts new file mode 100644 index 00000000..452a7618 --- /dev/null +++ b/scripts/migration-idempotency.test.ts @@ -0,0 +1,12 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("radio columns migration", () => { + it("adds every column idempotently for partially migrated databases", () => { + const sql = readFileSync(resolve("prisma/migrations/0009_radio_contests_giveaways_columns.sql"), "utf8"); + const additions = sql.match(/ADD COLUMN(?! IF NOT EXISTS)/gi) ?? []; + expect(additions).toEqual([]); + expect(sql).toContain("ADD COLUMN IF NOT EXISTS `title`"); + }); +});