Alexandre Bodin 29cb475d83 wip
2021-06-24 09:09:29 +02:00

338 lines
8.9 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);
},
};
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)) {
updatedProperties.push({ key, oldValue: srcValue, value });
} else {
unchangedProperties.push({ key, 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 diffTableColumns = (srcTable, destTable) => {
/*
for each column in dest table
if exists in src table
diff the two columns
check properties
if diff has changes push to changed
else push to unchanged
else push to added
for each column in src table
if not exists in dest table push to removed
*/
const addedColumns = [];
const updatedColumns = [];
const unchangedColumns = [];
const removedColumns = [];
for (const destColumn of destTable.columns) {
if (helpers.hasColumn(srcTable, destColumn.name)) {
const srcColumn = helpers.findColumn(srcTable, destColumn.name);
const { status, diff } = diffProperties(srcColumn, destColumn);
if (status === statuses.CHANGED) {
updatedColumns.push(diff);
} else {
unchangedColumns.push(srcColumn);
}
} else {
addedColumns.push(destColumn);
}
}
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) => {
/*
for each indexes in dest table
if exists in src table
diff the two indexes
check properties
if diff has changes push to changed
else push to unchanged
else push to added
for each indexes in src table
if not exists in dest table push to removed
*/
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) => {
/*
for each foreginKeys in dest table
if exists in src table
diff the two foreginKeys
check properties
if diff has changes push to changed
else push to unchanged
else push to added
for each foreginKeys in src table
if not exists in dest table push to removed
*/
const addedForeignKeys = [];
const updatedForeignKeys = [];
const unchangedForeignKeys = [];
const 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 hasChangedStatus = diff => diff.status === statuses.CHANGED;
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) => {
// NOTE: We could eventually try to detect renames in tables & columns or hint them when making changes
// TODO: let's try to see how we can avoid restarts completely by applying these dynamically in dev mode
/*
for each table in dest
if exists in src then
diff two tables
if diff has changes push to changed
else push to unchanged
else push to added
for each table in src check if it still exists
if table not in dest push to removed
*/
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,
},
},
};
};
module.exports = {
diffSchemas,
};