Removes duplicate rows from one or more translation tables, keeping only the most recently
updated row per (baseId, languageCode) pair.
Prior to the fix for a race condition in TranslatableSaver.update(), two concurrent
updates could both add a translation for the same language before either had committed,
resulting in two rows for the same (baseId, languageCode) pair. This is no longer
possible going forward, but a unique constraint on translation tables cannot be added to an
existing database that already contains such duplicates without first removing them.
This applies to any table backing a Translation<T> entity used with TranslatableSaver,
including translation tables defined by plugins for their own custom translatable entities —
not just the 13 core tables shown below.
You normally do not need to call this yourself. When vendure migrate generate detects that
the generated migration adds the unique constraint to one or more translation tables, it inserts
a deduplicateTranslations call for exactly those tables at the top of up(), ahead of the DDL.
Call it manually only if you write migrations by hand, or if you split the generated migration and
need the de-duplication to run ahead of a constraint in a separate file. It accepts a single
TranslationTableToDeduplicate or an array of them, and is a no-op for tables without
duplicates.
The example below shows the structure of a generated migration on Postgres. It is reformatted for
readability, and its comments are hand-written. The constraint names are deterministic, so
vendure migrate generate produces these exact names:
On MySQL/MariaDB, unique constraints are implemented as unique indexes, so the generated DDL
instead looks like CREATE UNIQUE INDEX `UQ_dcc35f0d2b8d422634e878b813c` ON `product_translation` (`languageCode`, `baseId`) (same deterministic names as above). On SQLite, adding a unique
constraint requires TypeORM to recreate the whole table, so the generated migration will
contain a much longer sequence of statements for each table; the generated
deduplicateTranslations call still precedes them.
A custom TypeORM naming strategy in dbConnectionOptions changes the physical column names. For
example, a snake_case strategy maps languageCode to language_code. In that case the generated
migration passes the physical column names for each affected table. The generated call is a single
line, shown here wrapped:
A table given by name only uses the default column names id, baseId, languageCode and
updatedAt. In the object form, each column missing from columns also uses its default name.
columns must hold the physical column names as they are when the migration runs.
Parameters
QueryRunnerArchitecture reviews, custom plugin work, migrations, ongoing support. Get a hand from the team that builds Vendure.
Talk to the team