Files
pole-book/server/node_modules/@strapi/database/dist/schema/diff.js

380 lines
15 KiB
JavaScript

'use strict';
var _ = require('lodash/fp');
// TODO: get that list dynamically instead
const RESERVED_TABLE_NAMES = [
'strapi_migrations',
'strapi_migrations_internal',
'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);
}
};
var createSchemaDiff = ((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 = [];
// use xor to avoid differences in order
if (_.xor(oldIndex.columns, index.columns).length > 0) {
changes.push('columns');
}
if (oldIndex.type && index.type && _.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 = (diffCtx)=>{
const { databaseTable, userSchemaTable, previousTable } = diffCtx;
const addedColumns = [];
const updatedColumns = [];
const unchangedColumns = [];
const removedColumns = [];
for (const userSchemaColumn of userSchemaTable.columns){
const databaseColumn = helpers.findColumn(databaseTable, userSchemaColumn.name);
if (databaseColumn) {
const { status, diff } = diffColumns(databaseColumn, userSchemaColumn);
if (status === statuses.CHANGED) {
updatedColumns.push(diff);
} else {
unchangedColumns.push(databaseColumn);
}
} else {
addedColumns.push(userSchemaColumn);
}
}
for (const databaseColumn of databaseTable.columns){
if (!helpers.hasColumn(userSchemaTable, databaseColumn.name) && previousTable && helpers.hasColumn(previousTable, databaseColumn.name)) {
removedColumns.push(databaseColumn);
}
}
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 = (diffCtx)=>{
const { databaseTable, userSchemaTable, previousTable } = diffCtx;
const addedIndexes = [];
const updatedIndexes = [];
const unchangedIndexes = [];
const removedIndexes = [];
for (const userSchemaIndex of userSchemaTable.indexes){
const databaseIndex = helpers.findIndex(databaseTable, userSchemaIndex.name);
if (databaseIndex) {
const { status, diff } = diffIndexes(databaseIndex, userSchemaIndex);
if (status === statuses.CHANGED) {
updatedIndexes.push(diff);
} else {
unchangedIndexes.push(databaseIndex);
}
} else {
addedIndexes.push(userSchemaIndex);
}
}
for (const databaseIndex of databaseTable.indexes){
if (!helpers.hasIndex(userSchemaTable, databaseIndex.name) && previousTable && helpers.hasIndex(previousTable, databaseIndex.name)) {
removedIndexes.push(databaseIndex);
}
}
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 = (diffCtx)=>{
const { databaseTable, userSchemaTable, previousTable } = diffCtx;
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 userSchemaForeignKeys of userSchemaTable.foreignKeys){
const databaseForeignKeys = helpers.findForeignKey(databaseTable, userSchemaForeignKeys.name);
if (databaseForeignKeys) {
const { status, diff } = diffForeignKeys(databaseForeignKeys, userSchemaForeignKeys);
if (status === statuses.CHANGED) {
updatedForeignKeys.push(diff);
} else {
unchangedForeignKeys.push(databaseForeignKeys);
}
} else {
addedForeignKeys.push(userSchemaForeignKeys);
}
}
for (const databaseForeignKeys of databaseTable.foreignKeys){
if (!helpers.hasForeignKey(userSchemaTable, databaseForeignKeys.name) && previousTable && helpers.hasForeignKey(previousTable, databaseForeignKeys.name)) {
removedForeignKeys.push(databaseForeignKeys);
}
}
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 = (diffCtx)=>{
const { databaseTable } = diffCtx;
const columnsDiff = diffTableColumns(diffCtx);
const indexesDiff = diffTableIndexes(diffCtx);
const foreignKeysDiff = diffTableForeignKeys(diffCtx);
const hasChanged = [
columnsDiff,
indexesDiff,
foreignKeysDiff
].some(hasChangedStatus);
return {
status: hasChanged ? statuses.CHANGED : statuses.UNCHANGED,
diff: {
name: databaseTable.name,
indexes: indexesDiff.diff,
foreignKeys: foreignKeysDiff.diff,
columns: columnsDiff.diff
}
};
};
const diffSchemas = async (schemaDiffCtx)=>{
const { previousSchema, databaseSchema, userSchema } = schemaDiffCtx;
const addedTables = [];
const updatedTables = [];
const unchangedTables = [];
const removedTables = [];
// for each table in the user schema, check if it already exists in the database schema
for (const userSchemaTable of userSchema.tables){
const databaseTable = helpers.findTable(databaseSchema, userSchemaTable.name);
const previousTable = previousSchema && helpers.findTable(previousSchema, userSchemaTable.name);
if (databaseTable) {
const { status, diff } = diffTables({
previousTable,
databaseTable,
userSchemaTable
});
if (status === statuses.CHANGED) {
updatedTables.push(diff);
} else {
unchangedTables.push(databaseTable);
}
} else {
addedTables.push(userSchemaTable);
}
}
// maintain audit logs table from EE -> CE
const parsePersistedTable = (persistedTable)=>{
if (typeof persistedTable === 'string') {
return persistedTable;
}
return persistedTable.name;
};
const persistedTables = helpers.hasTable(databaseSchema, 'strapi_core_store_settings') ? await strapi.store.get({
type: 'core',
key: 'persisted_tables'
}) ?? [] : [];
const reservedTables = [
...RESERVED_TABLE_NAMES,
...persistedTables.map(parsePersistedTable)
];
// for all tables in the database schema, check if they are not in the user schema
for (const databaseTable of databaseSchema.tables){
const isInUserSchema = helpers.hasTable(userSchema, databaseTable.name);
const wasTracked = previousSchema && helpers.hasTable(previousSchema, databaseTable.name);
const isReserved = reservedTables.includes(databaseTable.name);
// NOTE: if db table is not in the user schema and is not in the previous stored schema leave it alone. it is a user custom table that we should not touch
if (!isInUserSchema && !wasTracked) {
continue;
}
// if a db table is not in the user schema I want to delete it
if (!isInUserSchema && wasTracked && !isReserved) {
const dependencies = persistedTables.filter((table)=>{
const dependsOn = table?.dependsOn;
if (!_.isArray(dependsOn)) {
return;
}
return dependsOn.some((table)=>table.name === databaseTable.name);
}).map((dependsOnTable)=>{
return databaseSchema.tables.find((databaseTable)=>databaseTable.name === dependsOnTable.name);
})// In case the table is not found, filter undefined values
.filter((table)=>!_.isNil(table));
removedTables.push(databaseTable, ...dependencies);
}
}
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
};
});
module.exports = createSchemaDiff;
//# sourceMappingURL=diff.js.map