Skip to content

Commit

Permalink
run creation/update of the database even if the model hasnt changed
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre No毛l <petersg83@gmail.com>
  • Loading branch information
petersg83 committed Oct 14, 2020
1 parent 153e9a3 commit 3762fee
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 104 deletions.
180 changes: 98 additions & 82 deletions packages/strapi-connector-bookshelf/lib/build-database-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@ const _ = require('lodash');
const { singular } = require('pluralize');
const { contentTypes: contentTypesUtils } = require('strapi-utils');

const { storeDefinition, didDefinitionOrTableChange } = require('./utils/store-definition');
const { storeDefinition, didColumnDefinitionChange } = require('./utils/store-definition');
const { getDraftAndPublishMigrationWay, migrateDraftAndPublish } = require('./database-migration');
const { getManyRelations } = require('./utils/associations');

module.exports = async ({ ORM, loadedModel, definition, connection, model }) => {
const definitionDidChange = await didDefinitionOrTableChange(definition, ORM);
if (!definitionDidChange) {
return;
}

// Migrations to do before updating/creating the tables in database
const draftAndPublishMigrationWay = await getDraftAndPublishMigrationWay({ definition, ORM });
if (draftAndPublishMigrationWay === 'disable') {
await migrateDraftAndPublish({ definition, ORM, way: 'disable' });
}

// Update/create the tables in database
await createOrUpdateTables({ ORM, loadedModel, definition, connection, model });

// Migrations to do after updating/creating the tables in database
if (draftAndPublishMigrationWay === 'enable') {
await migrateDraftAndPublish({ definition, ORM, way: 'enable' });
}

await storeDefinition(definition, ORM);
};

const createOrUpdateTables = async ({ ORM, loadedModel, definition, connection, model }) => {
// Add created_at and updated_at field if timestamp option is true
if (loadedModel.hasTimestamps) {
definition.attributes[loadedModel.hasTimestamps[0]] = { type: 'currentTimestamp' };
Expand Down Expand Up @@ -102,12 +110,6 @@ module.exports = async ({ ORM, loadedModel, definition, connection, model }) =>
delete definition.attributes[loadedModel.hasTimestamps[0]];
delete definition.attributes[loadedModel.hasTimestamps[1]];
}

if (draftAndPublishMigrationWay === 'enable') {
await migrateDraftAndPublish({ definition, ORM, way: 'enable' });
}

await storeDefinition(definition, ORM);
};

const isColumn = ({ definition, attribute, name }) => {
Expand Down Expand Up @@ -277,21 +279,21 @@ const createOrUpdateTable = async ({ table, attributes, definition, ORM, model }
return;
}

const columns = Object.keys(attributes);
const attributesNames = Object.keys(attributes);

// Fetch existing column
const columnsExist = await Promise.all(
columns.map(attribute => ORM.knex.schema.hasColumn(table, attribute))
attributesNames.map(attribute => ORM.knex.schema.hasColumn(table, attribute))
);

const columnsToAdd = {};

// Get columns to add
columnsExist.forEach((columnExist, index) => {
const attribute = attributes[columns[index]];
const attribute = attributes[attributesNames[index]];

if (!columnExist) {
columnsToAdd[columns[index]] = attribute;
columnsToAdd[attributesNames[index]] = attribute;
}
});

Expand All @@ -302,83 +304,97 @@ const createOrUpdateTable = async ({ table, attributes, definition, ORM, model }
});
}

if (definition.client === 'sqlite3') {
const tmpTable = `tmp_${table}`;
const attrNamesWithoutTimestamps = attributesNames.filter(
attributeName => !(definition.options.timestamps || []).includes(attributeName)
);
const changedAttributesResult = await Promise.all(
attrNamesWithoutTimestamps.map(attributeName =>
didColumnDefinitionChange(attributeName, definition, ORM)
)
);
const columnsToAlter = attrNamesWithoutTimestamps.filter(
(attributeName, index) => changedAttributesResult[index]
);

const rebuildTable = async trx => {
await trx.schema.renameTable(table, tmpTable);
if (columnsToAlter.length > 0) {
if (definition.client === 'sqlite3') {
const tmpTable = `tmp_${table}`;

// drop possible conflicting indexes
await Promise.all(
columns.map(key => trx.raw('DROP INDEX IF EXISTS ??', uniqueColName(table, key)))
);
const rebuildTable = async trx => {
await trx.schema.renameTable(table, tmpTable);

// create the table
await createTable(table, { trx });
// drop possible conflicting indexes
await Promise.all(
attributesNames.map(key => trx.raw('DROP INDEX IF EXISTS ??', uniqueColName(table, key)))
);

const attrs = Object.keys(attributes).filter(attribute =>
isColumn({
definition,
attribute: attributes[attribute],
name: attribute,
})
);
// create the table
await createTable(table, { trx });

const allAttrs = ['id', ...attrs];
const attrs = attributesNames.filter(attributeName =>
isColumn({
definition,
attribute: attributes[attributeName],
name: attributeName,
})
);

await trx.insert(qb => qb.select(allAttrs).from(tmpTable)).into(table);
await trx.schema.dropTableIfExists(tmpTable);
};
const allAttrs = ['id', ...attrs];

try {
await ORM.knex.transaction(trx => rebuildTable(trx));
} catch (err) {
if (err.message.includes('UNIQUE constraint failed')) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.stack}`
);
} else {
strapi.log.error(`Migration failed`);
strapi.log.error(err);
}
await trx.insert(qb => qb.select(allAttrs).from(tmpTable)).into(table);
await trx.schema.dropTableIfExists(tmpTable);
};

return false;
}
} else {
const alterTable = async trx => {
await Promise.all(
columns.map(col => {
return ORM.knex.schema
.alterTable(table, tbl => {
tbl.dropUnique(col, uniqueColName(table, col));
})
.catch(() => {});
})
);
await trx.schema.alterTable(table, tbl => {
alterColumns(tbl, _.pick(attributes, columns), {
tableExists,
});
});
};
try {
await ORM.knex.transaction(trx => rebuildTable(trx));
} catch (err) {
if (err.message.includes('UNIQUE constraint failed')) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.stack}`
);
} else {
strapi.log.error(`Migration failed`);
strapi.log.error(err);
}

try {
await ORM.knex.transaction(trx => alterTable(trx));
} catch (err) {
if (err.code === '23505' && definition.client === 'pg') {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.message}\n\t- ${err.detail}`
);
} else if (definition.client === 'mysql' && err.errno === 1062) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.sqlMessage}`
);
} else {
strapi.log.error(`Migration failed`);
strapi.log.error(err);
return false;
}
} else {
const alterTable = async trx => {
await Promise.all(
columnsToAlter.map(col => {
return ORM.knex.schema
.alterTable(table, tbl => {
tbl.dropUnique(col, uniqueColName(table, col));
})
.catch(() => {});
})
);
await trx.schema.alterTable(table, tbl => {
alterColumns(tbl, _.pick(attributes, columnsToAlter), {
tableExists,
});
});
};

return false;
try {
await ORM.knex.transaction(trx => alterTable(trx));
} catch (err) {
if (err.code === '23505' && definition.client === 'pg') {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.message}\n\t- ${err.detail}`
);
} else if (definition.client === 'mysql' && err.errno === 1062) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${err.sqlMessage}`
);
} else {
strapi.log.error(`Migration failed`);
strapi.log.error(err);
}

return false;
}
}
}
};
6 changes: 3 additions & 3 deletions packages/strapi-connector-bookshelf/lib/mount-models.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ module.exports = async ({ models, target }, ctx, { selfFinalize = false } = {})

target[model].allAttributes = { ...definition.attributes };

const createAtCol = _.get(definition, 'options.timestamps.0', 'created_at');
const createdAtCol = _.get(definition, 'options.timestamps.0', 'created_at');
const updatedAtCol = _.get(definition, 'options.timestamps.1', 'updated_at');
if (_.get(definition, 'options.timestamps', false)) {
_.set(definition, 'options.timestamps', [createAtCol, updatedAtCol]);
target[model].allAttributes[createAtCol] = { type: 'timestamp' };
_.set(definition, 'options.timestamps', [createdAtCol, updatedAtCol]);
target[model].allAttributes[createdAtCol] = { type: 'timestamp' };
target[model].allAttributes[updatedAtCol] = { type: 'timestamp' };
} else {
_.set(definition, 'options.timestamps', false);
Expand Down
26 changes: 7 additions & 19 deletions packages/strapi-connector-bookshelf/lib/utils/store-definition.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const _ = require('lodash');
const { getManyRelations } = require('./associations');

const formatDefinitionToStore = definition =>
JSON.stringify(
Expand Down Expand Up @@ -39,29 +38,18 @@ const storeDefinition = async (definition, ORM) => {
return strapi.models['core_store'].forge(defData).save();
};

const didDefinitionOrTableChange = async (definition, ORM) => {
// Checks if the tables exist in DB
const manyRelations = getManyRelations(definition);
const tablesToCheck = manyRelations.map(r => r.tableCollectionName);
tablesToCheck.push(definition.collectionName);

for (const tableToCheck of tablesToCheck) {
const tableExists = await ORM.knex.schema.hasTable(tableToCheck);
if (!tableExists) {
return true;
}
}

const didColumnDefinitionChange = async (attributeName, definition, ORM) => {
// Checks if the definition has changed
const previousDefRow = await getDefinitionFromStore(definition, ORM);
const previousDefJSON = _.get(previousDefRow, 'value', null);
const actualDefJSON = formatDefinitionToStore(definition);
const previousDefinitionRow = await getDefinitionFromStore(definition, ORM);
const previousDefinition = JSON.parse(_.get(previousDefinitionRow, 'value', null));
const previousAttribute = _.get(previousDefinition, ['attributes', attributeName], null);
const actualAttribute = _.get(definition, ['attributes', attributeName], null);

return previousDefJSON !== actualDefJSON;
return !_.isEqual(previousAttribute, actualAttribute);
};

module.exports = {
storeDefinition,
didDefinitionOrTableChange,
getDefinitionFromStore,
didColumnDefinitionChange,
};

0 comments on commit 3762fee

Please sign in to comment.