@@ -13,28 +13,62 @@ import { QueryRunner } from 'typeorm';
1313 * existing database that already contains such duplicates without first removing them.
1414 *
1515 * Call this from your migration's `up()` method **before** the unique constraint on the
16- * translation table is created.
16+ * translation table is created, once per affected table. `vendure migrate generate` will
17+ * generate the DDL that creates the constraints; you only need to add the
18+ * `deduplicateTranslations` calls ahead of it. On Postgres this looks like (constraint names
19+ * are deterministic, so `vendure migrate generate` will produce these exact names):
1720 *
1821 * ```ts
1922 * import { MigrationInterface, QueryRunner } from 'typeorm';
2023 * import { deduplicateTranslations } from '\@vendure/core';
2124 *
22- * export class AddTranslationUniqueConstraint1234567890 implements MigrationInterface {
25+ * export class AddTranslationUniqueConstraints1234567890 implements MigrationInterface {
2326 * public async up(queryRunner: QueryRunner): Promise<any> {
24- * // --- Remove any pre-existing duplicate rows ---
27+ * // --- Remove any pre-existing duplicate rows, one call per translation table ---
2528 * await deduplicateTranslations(queryRunner, 'product_translation');
29+ * await deduplicateTranslations(queryRunner, 'product_variant_translation');
30+ * await deduplicateTranslations(queryRunner, 'product_option_translation');
31+ * await deduplicateTranslations(queryRunner, 'product_option_group_translation');
32+ * await deduplicateTranslations(queryRunner, 'collection_translation');
33+ * await deduplicateTranslations(queryRunner, 'facet_translation');
34+ * await deduplicateTranslations(queryRunner, 'facet_value_translation');
35+ * await deduplicateTranslations(queryRunner, 'asset_translation');
36+ * await deduplicateTranslations(queryRunner, 'api_key_translation');
37+ * await deduplicateTranslations(queryRunner, 'payment_method_translation');
38+ * await deduplicateTranslations(queryRunner, 'promotion_translation');
39+ * await deduplicateTranslations(queryRunner, 'region_translation');
40+ * await deduplicateTranslations(queryRunner, 'shipping_method_translation');
2641 *
27- * // --- Auto-generated DDL continues ---
28- * // (Create the unique index/constraint)
29- * // ...
42+ * // --- Auto-generated DDL continues (Postgres) ---
43+ * await queryRunner.query(`ALTER TABLE "product_translation" ADD CONSTRAINT "UQ_dcc35f0d2b8d422634e878b813c" UNIQUE ("languageCode", "baseId")`);
44+ * await queryRunner.query(`ALTER TABLE "product_variant_translation" ADD CONSTRAINT "UQ_33042b9e7ea5dcf5ec09a5a4130" UNIQUE ("languageCode", "baseId")`);
45+ * await queryRunner.query(`ALTER TABLE "product_option_translation" ADD CONSTRAINT "UQ_bd426ff614344b6759d327fe58c" UNIQUE ("languageCode", "baseId")`);
46+ * await queryRunner.query(`ALTER TABLE "product_option_group_translation" ADD CONSTRAINT "UQ_bb3992fff02944c061363d9d015" UNIQUE ("languageCode", "baseId")`);
47+ * await queryRunner.query(`ALTER TABLE "collection_translation" ADD CONSTRAINT "UQ_858c112351f7714960a2dfcff91" UNIQUE ("languageCode", "baseId")`);
48+ * await queryRunner.query(`ALTER TABLE "facet_translation" ADD CONSTRAINT "UQ_c9174d39ac643e5f14b73ad6cb5" UNIQUE ("languageCode", "baseId")`);
49+ * await queryRunner.query(`ALTER TABLE "facet_value_translation" ADD CONSTRAINT "UQ_084f223aa69bcd7683e727e3130" UNIQUE ("languageCode", "baseId")`);
50+ * await queryRunner.query(`ALTER TABLE "asset_translation" ADD CONSTRAINT "UQ_0dad4e3837a685fea5062da3d96" UNIQUE ("languageCode", "baseId")`);
51+ * await queryRunner.query(`ALTER TABLE "api_key_translation" ADD CONSTRAINT "UQ_0a5a4e1c3fb9547a20f34ef2240" UNIQUE ("languageCode", "baseId")`);
52+ * await queryRunner.query(`ALTER TABLE "payment_method_translation" ADD CONSTRAINT "UQ_5ee20e71a427f7d86e921083777" UNIQUE ("languageCode", "baseId")`);
53+ * await queryRunner.query(`ALTER TABLE "promotion_translation" ADD CONSTRAINT "UQ_0d74bcbf35d65e5d7c2a7947ae1" UNIQUE ("languageCode", "baseId")`);
54+ * await queryRunner.query(`ALTER TABLE "region_translation" ADD CONSTRAINT "UQ_fc1a4a3618bcd16cc50dd50dfeb" UNIQUE ("languageCode", "baseId")`);
55+ * await queryRunner.query(`ALTER TABLE "shipping_method_translation" ADD CONSTRAINT "UQ_6f2a231486b87fd9d337bf9d60b" UNIQUE ("languageCode", "baseId")`);
3056 * }
3157 *
3258 * public async down(queryRunner: QueryRunner): Promise<any> {
33- * // Auto-generated reverse DDL
59+ * // Auto-generated reverse DDL — drops the constraints added above.
60+ * // ...
3461 * }
3562 * }
3663 * ```
3764 *
65+ * On MySQL/MariaDB, unique constraints are implemented as unique indexes, so the generated DDL
66+ * instead looks like `` CREATE UNIQUE INDEX `UQ_dcc35f0d2b8d422634e878b813c` ON `product_translation`
67+ * (`languageCode`, `baseId`) `` (same deterministic names as above). On SQLite, adding a unique
68+ * constraint requires TypeORM to recreate the whole table, so the generated migration will
69+ * contain a much longer sequence of statements for each table — call `deduplicateTranslations`
70+ * before that generated block in the same way.
71+ *
3872 * @docsCategory migration
3973 */
4074export async function deduplicateTranslations (
0 commit comments