mirror of
https://github.com/strapi/strapi.git
synced 2025-07-23 17:10:08 +00:00
356 lines
10 KiB
JavaScript
356 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const _ = require('lodash/fp');
|
|
|
|
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;
|
|
|
|
const diffProperties = (srcObject, destObject) => {
|
|
const addedProperties = [];
|
|
const updatedProperties = [];
|
|
const unchangedProperties = [];
|
|
const removedProperties = [];
|
|
|
|
for (const key in destObject) {
|
|
const value = destObject[key];
|
|
|
|
if (_.has(key, srcObject)) {
|
|
const srcValue = srcObject[key];
|
|
|
|
if (_.isEqual(srcValue, value)) {
|
|
unchangedProperties.push({ key, value });
|
|
} else {
|
|
updatedProperties.push({ key, oldValue: srcValue, value });
|
|
}
|
|
} else {
|
|
addedProperties.push({ key, value });
|
|
}
|
|
}
|
|
|
|
for (const key in srcObject) {
|
|
const value = srcObject[key];
|
|
|
|
if (!_.has(key, destObject)) {
|
|
removedProperties.push({ key, oldValue: value });
|
|
}
|
|
}
|
|
|
|
const hasChanged = [addedProperties, updatedProperties, removedProperties].some(
|
|
arr => arr.length > 0
|
|
);
|
|
|
|
return {
|
|
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
|
|
diff: {
|
|
name: destObject.name,
|
|
object: destObject,
|
|
// NOTE: maybe put into properties: {}
|
|
added: addedProperties,
|
|
updated: updatedProperties,
|
|
unchanged: unchangedProperties,
|
|
removed: removedProperties,
|
|
},
|
|
};
|
|
};
|
|
|
|
const diffDefault = (oldColumn, column) => {
|
|
const oldDefaultTo = oldColumn.defaultTo;
|
|
const defaultTo = column.defaultTo;
|
|
|
|
if (oldDefaultTo === null || _.toLower(oldDefaultTo) === 'null') {
|
|
return _.isNil(defaultTo) || _.toLower(defaultTo) === 'null';
|
|
}
|
|
|
|
if (!_.isNil(oldDefaultTo) && !_.isNil(defaultTo)) {
|
|
return _.toLower(oldDefaultTo) === _.toLower(`'${column.defaultTo}'`);
|
|
}
|
|
|
|
return oldDefaultTo === 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 = [];
|
|
|
|
// NOTE: we might want to move that to the schema generation instead
|
|
// NOTE: enum aren't updated, they need to be dropped & recreated. Knex doesn't handle it
|
|
const oldType = oldColumn.type;
|
|
const type = db.dialect.getSqlType(column.type);
|
|
if (oldType !== type && !['increments', 'enum'].includes(type)) {
|
|
changes.push('type');
|
|
}
|
|
|
|
if (!_.isEqual(oldColumn.args, column.args) && !['increments', 'enum'].includes(type)) {
|
|
changes.push('args');
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
if (changes.length > 0) {
|
|
console.log(`Changes in ${column.name}, ${changes}`);
|
|
}
|
|
|
|
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 } = diffProperties(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 } = diffProperties(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 = (srcSchema, destSchema) => {
|
|
const addedTables = [];
|
|
const updatedTables = [];
|
|
const unchangedTables = [];
|
|
const removedTables = [];
|
|
|
|
for (const destTable of destSchema.tables) {
|
|
if (helpers.hasTable(srcSchema, destTable.name)) {
|
|
// either changed or unchanged
|
|
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);
|
|
}
|
|
}
|
|
|
|
for (const srcTable of srcSchema.tables) {
|
|
if (!helpers.hasTable(destSchema, 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,
|
|
};
|
|
};
|