185 lines
5.7 KiB
JavaScript
Raw Normal View History

2021-02-18 18:42:28 +01:00
'use strict';
const { difference, pick, orderBy, prop, intersection } = require('lodash/fp');
const { getService } = require('../../../utils');
2021-02-25 17:40:14 +01:00
const BATCH_SIZE = 1000;
2021-03-01 11:26:44 +01:00
const shouldBeProcesseed = processedLocaleCodes => entry => {
return (
entry.localizations.length > 1 &&
intersection(entry.localizations.map(prop('locale')), processedLocaleCodes).length === 0
);
};
const getUpdates = ({ entriesToProcess, formatUpdate, locale, attributesToMigrate }) => {
return entriesToProcess.reduce((updates, entry) => {
const attributesValues = pick(attributesToMigrate, entry);
const entriesIdsToUpdate = entry.localizations
.filter(related => related.locale !== locale.code)
.map(prop('id'));
return updates.concat(formatUpdate(entriesIdsToUpdate, attributesValues));
}, []);
};
const formatMongooseUpdate = (entriesIdsToUpdate, attributesValues) => ({
updateMany: { filter: { _id: { $in: entriesIdsToUpdate } }, update: attributesValues },
});
const formatBookshelfUpdate = (entriesIdsToUpdate, attributesValues) =>
entriesIdsToUpdate.map(id => ({ id, ...attributesValues }));
2021-02-25 17:40:14 +01:00
const migrateForBookshelf = async ({ ORM, model, attributesToMigrate, locales }) => {
2021-03-01 11:26:44 +01:00
// Create tmp table with all updates to make (faster than making updates one by one)
2021-02-25 17:40:14 +01:00
const TMP_TABLE_NAME = '__tmp__i18n_field_migration';
const columnsToCopy = ['id', ...attributesToMigrate];
2021-03-01 11:26:44 +01:00
2021-02-25 17:40:14 +01:00
await ORM.knex.schema.dropTableIfExists(TMP_TABLE_NAME);
await ORM.knex.raw(`CREATE TABLE ?? AS ??`, [
TMP_TABLE_NAME,
ORM.knex
.select(columnsToCopy)
.from(model.collectionName)
.whereRaw('?', 0),
]);
// Transaction is started after DDL because of MySQL (https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html)
const trx = await ORM.knex.transaction();
try {
const processedLocaleCodes = [];
for (const locale of locales) {
let offset = 0;
let batchCount = BATCH_SIZE;
2021-03-01 11:26:44 +01:00
while (batchCount === BATCH_SIZE) {
2021-02-25 17:40:14 +01:00
const batch = await trx
.select([...attributesToMigrate, 'locale', 'localizations'])
.from(model.collectionName)
.where('locale', locale.code)
.orderBy('id')
.offset(offset)
2021-03-01 11:26:44 +01:00
.limit(BATCH_SIZE);
2021-02-18 18:42:28 +01:00
2021-03-01 11:26:44 +01:00
// postgres automatically parses JSON, but not sqlite nor mysql
2021-02-25 17:40:14 +01:00
batch.forEach(entry => {
if (typeof entry.localizations === 'string') {
entry.localizations = JSON.parse(entry.localizations);
}
});
batchCount = batch.length;
2021-03-01 11:26:44 +01:00
const entriesToProcess = batch.filter(shouldBeProcesseed(processedLocaleCodes));
2021-02-25 17:40:14 +01:00
2021-03-01 11:26:44 +01:00
const tmpEntries = getUpdates({
entriesToProcess,
formatUpdate: formatBookshelfUpdate,
locale,
attributesToMigrate,
});
2021-02-25 17:40:14 +01:00
2021-03-01 11:26:44 +01:00
await trx.batchInsert(TMP_TABLE_NAME, tmpEntries, 100);
2021-02-25 17:40:14 +01:00
2021-03-01 11:26:44 +01:00
offset += BATCH_SIZE;
2021-02-25 17:40:14 +01:00
}
processedLocaleCodes.push(locale.code);
}
2021-03-01 11:26:44 +01:00
const getSubquery = columnName =>
2021-02-25 17:40:14 +01:00
trx
2021-03-01 11:26:44 +01:00
.select(columnName)
2021-02-25 17:40:14 +01:00
.from(TMP_TABLE_NAME)
.where(`${TMP_TABLE_NAME}.id`, trx.raw('??', [`${model.collectionName}.id`]));
2021-03-01 11:26:44 +01:00
2021-02-25 17:40:14 +01:00
const updates = attributesToMigrate.reduce(
2021-03-01 11:26:44 +01:00
(updates, columnName) => ({ ...updates, [columnName]: getSubquery(columnName) }),
2021-02-25 17:40:14 +01:00
{}
);
2021-03-01 11:26:44 +01:00
2021-02-25 17:40:14 +01:00
await trx
.from(model.collectionName)
.update(updates)
.whereIn('id', qb => qb.select(['id']).from(TMP_TABLE_NAME));
// Transaction is ended before DDL
await trx.commit();
await ORM.knex.schema.dropTableIfExists(TMP_TABLE_NAME);
} catch (e) {
await trx.rollback();
throw e;
}
};
const migrateForMongoose = async ({ model, attributesToMigrate, locales }) => {
const processedLocaleCodes = [];
for (const locale of locales) {
let batchCount = BATCH_SIZE;
let lastId;
2021-03-01 11:26:44 +01:00
while (batchCount === BATCH_SIZE) {
2021-02-25 17:40:14 +01:00
const findParams = { locale: locale.code };
if (lastId) {
findParams._id = { $gt: lastId };
}
const batch = await model
.find(findParams, [...attributesToMigrate, 'locale', 'localizations'])
.sort({ _id: 1 })
2021-03-01 11:26:44 +01:00
.limit(BATCH_SIZE);
2021-02-25 17:40:14 +01:00
if (batch.length > 0) {
lastId = batch[batch.length - 1]._id;
}
batchCount = batch.length;
2021-03-01 11:26:44 +01:00
const entriesToProcess = batch.filter(shouldBeProcesseed);
2021-02-25 17:40:14 +01:00
2021-03-01 11:26:44 +01:00
const updates = getUpdates({
entriesToProcess,
formatUpdate: formatMongooseUpdate,
locale,
attributesToMigrate,
});
2021-02-25 17:40:14 +01:00
await model.bulkWrite(updates);
}
processedLocaleCodes.push(locale.code);
}
};
2021-02-18 18:42:28 +01:00
2021-02-23 16:52:30 +01:00
// Migration when i18n is disabled on a field of a content-type that have i18n enabled
2021-02-18 18:42:28 +01:00
const after = async ({ model, definition, previousDefinition, ORM }) => {
const ctService = getService('content-types');
const localeService = getService('locales');
if (!ctService.isLocalized(model)) {
return;
}
const localizedAttributes = ctService.getLocalizedAttributes(definition);
const prevLocalizedAttributes = ctService.getLocalizedAttributes(previousDefinition);
const attributesDisabled = difference(prevLocalizedAttributes, localizedAttributes);
const attributesToMigrate = intersection(Object.keys(definition.attributes), attributesDisabled);
if (attributesToMigrate.length === 0) {
return;
}
let locales = await localeService.find();
locales = await localeService.setIsDefault(locales);
locales = orderBy(['isDefault', 'code'], ['desc', 'asc'])(locales); // Put default locale first
if (model.orm === 'bookshelf') {
2021-02-25 17:40:14 +01:00
await migrateForBookshelf({ ORM, model, attributesToMigrate, locales });
2021-02-18 18:42:28 +01:00
} else if (model.orm === 'mongoose') {
2021-02-25 17:40:14 +01:00
await migrateForMongoose({ model, attributesToMigrate, locales });
2021-02-18 18:42:28 +01:00
}
};
2021-02-25 17:40:14 +01:00
const before = () => {};
2021-02-18 18:42:28 +01:00
module.exports = {
before,
after,
};