mirror of
https://github.com/strapi/strapi.git
synced 2025-07-21 07:57:45 +00:00
382 lines
11 KiB
JavaScript
382 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
const _ = require('lodash/fp');
|
|
|
|
const RESERVED_TABLE_NAMES = ['strapi_migrations', 'strapi_database_schema'];
|
|
|
|
const statuses = {
|
|
CHANGED: 'CHANGED',
|
|
UNCHANGED: 'UNCHANGED',
|
|
};
|
|
|
|
// NOTE:We could move the schema to use maps of tables & columns instead of arrays to make it easier to diff
|
|
// => this will make the creation a bit more complicated (ordering, Object.values(tables | columns)) -> not a big pbl
|
|
|
|
const helpers = {
|
|
hasTable(schema, tableName) {
|
|
return schema.tables.findIndex((table) => table.name === tableName) !== -1;
|
|
},
|
|
findTable(schema, tableName) {
|
|
return schema.tables.find((table) => table.name === tableName);
|
|
},
|
|
|
|
hasColumn(table, columnName) {
|
|
return table.columns.findIndex((column) => column.name === columnName) !== -1;
|
|
},
|
|
findColumn(table, columnName) {
|
|
return table.columns.find((column) => column.name === columnName);
|
|
},
|
|
|
|
hasIndex(table, columnName) {
|
|
return table.indexes.findIndex((column) => column.name === columnName) !== -1;
|
|
},
|
|
findIndex(table, columnName) {
|
|
return table.indexes.find((column) => column.name === columnName);
|
|
},
|
|
|
|
hasForeignKey(table, columnName) {
|
|
return table.foreignKeys.findIndex((column) => column.name === columnName) !== -1;
|
|
},
|
|
findForeignKey(table, columnName) {
|
|
return table.foreignKeys.find((column) => column.name === columnName);
|
|
},
|
|
};
|
|
|
|
module.exports = (db) => {
|
|
const hasChangedStatus = (diff) => diff.status === statuses.CHANGED;
|
|
|
|
/**
|
|
* Compares two indexes info
|
|
* @param {Object} oldIndex - index info read from DB
|
|
* @param {Object} index - newly generate index info
|
|
*/
|
|
const diffIndexes = (oldIndex, index) => {
|
|
const changes = [];
|
|
|
|
if (!_.isEqual(oldIndex.columns, index.columns)) {
|
|
changes.push('columns');
|
|
}
|
|
|
|
if (_.toLower(oldIndex.type) !== _.toLower(index.type)) {
|
|
changes.push('type');
|
|
}
|
|
|
|
return {
|
|
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
name: index.name,
|
|
object: index,
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Compares two foreign keys info
|
|
* @param {Object} oldForeignKey - foreignKey info read from DB
|
|
* @param {Object} foreignKey - newly generate foreignKey info
|
|
*/
|
|
const diffForeignKeys = (oldForeignKey, foreignKey) => {
|
|
const changes = [];
|
|
|
|
if (_.difference(oldForeignKey.columns, foreignKey.columns).length > 0) {
|
|
changes.push('columns');
|
|
}
|
|
|
|
if (_.difference(oldForeignKey.referencedColumns, foreignKey.referencedColumns).length > 0) {
|
|
changes.push('referencedColumns');
|
|
}
|
|
|
|
if (oldForeignKey.referencedTable !== foreignKey.referencedTable) {
|
|
changes.push('referencedTable');
|
|
}
|
|
|
|
if (_.isNil(oldForeignKey.onDelete) || _.toUpper(oldForeignKey.onDelete) === 'NO ACTION') {
|
|
if (!_.isNil(foreignKey.onDelete) && _.toUpper(oldForeignKey.onDelete) !== 'NO ACTION') {
|
|
changes.push('onDelete');
|
|
}
|
|
} else if (_.toUpper(oldForeignKey.onDelete) !== _.toUpper(foreignKey.onDelete)) {
|
|
changes.push('onDelete');
|
|
}
|
|
|
|
if (_.isNil(oldForeignKey.onUpdate) || _.toUpper(oldForeignKey.onUpdate) === 'NO ACTION') {
|
|
if (!_.isNil(foreignKey.onUpdate) && _.toUpper(oldForeignKey.onUpdate) !== 'NO ACTION') {
|
|
changes.push('onUpdate');
|
|
}
|
|
} else if (_.toUpper(oldForeignKey.onUpdate) !== _.toUpper(foreignKey.onUpdate)) {
|
|
changes.push('onUpdate');
|
|
}
|
|
|
|
return {
|
|
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
name: foreignKey.name,
|
|
object: foreignKey,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffDefault = (oldColumn, column) => {
|
|
const oldDefaultTo = oldColumn.defaultTo;
|
|
const { defaultTo } = column;
|
|
|
|
if (oldDefaultTo === null || _.toLower(oldDefaultTo) === 'null') {
|
|
return _.isNil(defaultTo) || _.toLower(defaultTo) === 'null';
|
|
}
|
|
|
|
return (
|
|
_.toLower(oldDefaultTo) === _.toLower(column.defaultTo) ||
|
|
_.toLower(oldDefaultTo) === _.toLower(`'${column.defaultTo}'`)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Compares two columns info
|
|
* @param {Object} oldColumn - column info read from DB
|
|
* @param {Object} column - newly generate column info
|
|
*/
|
|
const diffColumns = (oldColumn, column) => {
|
|
const changes = [];
|
|
|
|
const isIgnoredType = ['increments'].includes(column.type);
|
|
const oldType = oldColumn.type;
|
|
const type = db.dialect.getSqlType(column.type);
|
|
|
|
if (oldType !== type && !isIgnoredType) {
|
|
changes.push('type');
|
|
}
|
|
|
|
// NOTE: compare args at some point and split them into specific properties instead
|
|
|
|
if (oldColumn.notNullable !== column.notNullable) {
|
|
changes.push('notNullable');
|
|
}
|
|
|
|
const hasSameDefault = diffDefault(oldColumn, column);
|
|
if (!hasSameDefault) {
|
|
changes.push('defaultTo');
|
|
}
|
|
|
|
if (oldColumn.unsigned !== column.unsigned && db.dialect.supportsUnsigned()) {
|
|
changes.push('unsigned');
|
|
}
|
|
|
|
return {
|
|
status: changes.length > 0 ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
name: column.name,
|
|
object: column,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffTableColumns = (srcTable, destTable) => {
|
|
const addedColumns = [];
|
|
const updatedColumns = [];
|
|
const unchangedColumns = [];
|
|
const removedColumns = [];
|
|
|
|
for (const destColumn of destTable.columns) {
|
|
if (!helpers.hasColumn(srcTable, destColumn.name)) {
|
|
addedColumns.push(destColumn);
|
|
continue;
|
|
}
|
|
|
|
const srcColumn = helpers.findColumn(srcTable, destColumn.name);
|
|
const { status, diff } = diffColumns(srcColumn, destColumn);
|
|
|
|
if (status === statuses.CHANGED) {
|
|
updatedColumns.push(diff);
|
|
} else {
|
|
unchangedColumns.push(srcColumn);
|
|
}
|
|
}
|
|
|
|
for (const srcColumn of srcTable.columns) {
|
|
if (!helpers.hasColumn(destTable, srcColumn.name)) {
|
|
removedColumns.push(srcColumn);
|
|
}
|
|
}
|
|
|
|
const hasChanged = [addedColumns, updatedColumns, removedColumns].some((arr) => arr.length > 0);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
added: addedColumns,
|
|
updated: updatedColumns,
|
|
unchanged: unchangedColumns,
|
|
removed: removedColumns,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffTableIndexes = (srcTable, destTable) => {
|
|
const addedIndexes = [];
|
|
const updatedIndexes = [];
|
|
const unchangedIndexes = [];
|
|
const removedIndexes = [];
|
|
|
|
for (const destIndex of destTable.indexes) {
|
|
if (helpers.hasIndex(srcTable, destIndex.name)) {
|
|
const srcIndex = helpers.findIndex(srcTable, destIndex.name);
|
|
const { status, diff } = diffIndexes(srcIndex, destIndex);
|
|
|
|
if (status === statuses.CHANGED) {
|
|
updatedIndexes.push(diff);
|
|
} else {
|
|
unchangedIndexes.push(srcIndex);
|
|
}
|
|
} else {
|
|
addedIndexes.push(destIndex);
|
|
}
|
|
}
|
|
|
|
for (const srcIndex of srcTable.indexes) {
|
|
if (!helpers.hasIndex(destTable, srcIndex.name)) {
|
|
removedIndexes.push(srcIndex);
|
|
}
|
|
}
|
|
|
|
const hasChanged = [addedIndexes, updatedIndexes, removedIndexes].some((arr) => arr.length > 0);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
added: addedIndexes,
|
|
updated: updatedIndexes,
|
|
unchanged: unchangedIndexes,
|
|
removed: removedIndexes,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffTableForeignKeys = (srcTable, destTable) => {
|
|
const addedForeignKeys = [];
|
|
const updatedForeignKeys = [];
|
|
const unchangedForeignKeys = [];
|
|
const removedForeignKeys = [];
|
|
|
|
if (!db.dialect.usesForeignKeys()) {
|
|
return {
|
|
status: statuses.UNCHANGED,
|
|
diff: {
|
|
added: addedForeignKeys,
|
|
updated: updatedForeignKeys,
|
|
unchanged: unchangedForeignKeys,
|
|
removed: removedForeignKeys,
|
|
},
|
|
};
|
|
}
|
|
|
|
for (const destForeignKey of destTable.foreignKeys) {
|
|
if (helpers.hasForeignKey(srcTable, destForeignKey.name)) {
|
|
const srcForeignKey = helpers.findForeignKey(srcTable, destForeignKey.name);
|
|
const { status, diff } = diffForeignKeys(srcForeignKey, destForeignKey);
|
|
|
|
if (status === statuses.CHANGED) {
|
|
updatedForeignKeys.push(diff);
|
|
} else {
|
|
unchangedForeignKeys.push(srcForeignKey);
|
|
}
|
|
} else {
|
|
addedForeignKeys.push(destForeignKey);
|
|
}
|
|
}
|
|
|
|
for (const srcForeignKey of srcTable.foreignKeys) {
|
|
if (!helpers.hasForeignKey(destTable, srcForeignKey.name)) {
|
|
removedForeignKeys.push(srcForeignKey);
|
|
}
|
|
}
|
|
|
|
const hasChanged = [addedForeignKeys, updatedForeignKeys, removedForeignKeys].some(
|
|
(arr) => arr.length > 0
|
|
);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
added: addedForeignKeys,
|
|
updated: updatedForeignKeys,
|
|
unchanged: unchangedForeignKeys,
|
|
removed: removedForeignKeys,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffTables = (srcTable, destTable) => {
|
|
const columnsDiff = diffTableColumns(srcTable, destTable);
|
|
const indexesDiff = diffTableIndexes(srcTable, destTable);
|
|
const foreignKeysDiff = diffTableForeignKeys(srcTable, destTable);
|
|
|
|
const hasChanged = [columnsDiff, indexesDiff, foreignKeysDiff].some(hasChangedStatus);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
name: srcTable.name,
|
|
indexes: indexesDiff.diff,
|
|
foreignKeys: foreignKeysDiff.diff,
|
|
columns: columnsDiff.diff,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffSchemas = async (srcSchema, destSchema) => {
|
|
const addedTables = [];
|
|
const updatedTables = [];
|
|
const unchangedTables = [];
|
|
const removedTables = [];
|
|
|
|
for (const destTable of destSchema.tables) {
|
|
if (helpers.hasTable(srcSchema, destTable.name)) {
|
|
const srcTable = helpers.findTable(srcSchema, destTable.name);
|
|
|
|
const { status, diff } = diffTables(srcTable, destTable);
|
|
|
|
if (status === statuses.CHANGED) {
|
|
updatedTables.push(diff);
|
|
} else {
|
|
unchangedTables.push(srcTable);
|
|
}
|
|
} else {
|
|
addedTables.push(destTable);
|
|
}
|
|
}
|
|
|
|
const persistedTables = helpers.hasTable(srcSchema, 'strapi_core_store_settings')
|
|
? (await strapi.store.get({
|
|
type: 'core',
|
|
key: 'persisted_tables',
|
|
})) ?? []
|
|
: [];
|
|
|
|
for (const srcTable of srcSchema.tables) {
|
|
if (
|
|
!helpers.hasTable(destSchema, srcTable.name) &&
|
|
![...RESERVED_TABLE_NAMES, ...persistedTables].includes(srcTable.name)
|
|
) {
|
|
removedTables.push(srcTable);
|
|
}
|
|
}
|
|
|
|
const hasChanged = [addedTables, updatedTables, removedTables].some((arr) => arr.length > 0);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
tables: {
|
|
added: addedTables,
|
|
updated: updatedTables,
|
|
unchanged: unchangedTables,
|
|
removed: removedTables,
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
return {
|
|
diff: diffSchemas,
|
|
};
|
|
};
|