Merge pull request #11557 from strapi/v4/database-improvements

Support schema option and add forceMigration options
This commit is contained in:
Alexandre BODIN 2021-11-16 12:06:54 +01:00 committed by GitHub
commit 3b11037883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 53 deletions

View File

@ -123,10 +123,6 @@ class MysqlSchemaInspector {
return schema;
}
getDatabaseSchema() {
return this.db.connection.client.connectionSettings.schema || 'public';
}
async getTables() {
const [rows] = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);

View File

@ -139,7 +139,7 @@ class PostgresqlSchemaInspector {
}
getDatabaseSchema() {
return this.db.connection.client.connectionSettings.schema || 'public';
return this.db.connection.getSchemaName() || 'public';
}
async getTables() {

View File

@ -13,16 +13,32 @@ const errors = require('./errors');
// TODO: move back into strapi
const { transformContentTypes } = require('./utils/content-types');
const createConnection = config => {
const knexInstance = knex(config);
return Object.assign(knexInstance, {
getSchemaName() {
return this.client.connectionSettings.schema;
},
});
};
class Database {
constructor(config) {
this.metadata = createMetadata(config.models);
this.config = config;
this.config = {
connection: {},
settings: {
forceMigration: true,
},
...config,
};
this.dialect = getDialect(this);
this.dialect.configure();
this.connection = knex(this.config.connection);
this.connection = createConnection(this.config.connection);
this.dialect.initialize();
@ -42,6 +58,17 @@ class Database {
return this.entityManager.getRepository(uid);
}
getConnection(tableName) {
const schema = this.connection.getSchemaName();
const connection = tableName ? this.connection(tableName) : this.connection;
return schema ? connection.withSchema(schema) : connection;
}
getSchemaConnection(trx = this.connection) {
const schema = this.connection.getSchemaName();
return schema ? trx.schema.withSchema(schema) : trx.schema;
}
queryBuilder(uid) {
return this.entityManager.createQueryBuilder(uid);
}

View File

@ -27,7 +27,7 @@ const createUmzugProvider = db => {
fse.ensureDirSync(migrationDir);
const wrapFn = fn => db => db.connection.transaction(trx => Promise.resolve(fn(trx)));
const wrapFn = fn => db => db.getConnection().transaction(trx => Promise.resolve(fn(trx)));
const storage = createStorage({ db, tableName: 'strapi_migrations' });
return new Umzug({

View File

@ -1,13 +1,12 @@
'use strict';
const createStorage = (opts = {}) => {
const tableName = opts.tableName || 'strapi_migrations';
const knex = opts.db.connection;
const { db, tableName = 'strapi_migrations' } = opts;
const hasMigrationTable = () => knex.schema.hasTable(tableName);
const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
const createMigrationTable = () => {
return knex.schema.createTable(tableName, table => {
return db.getSchemaConnection().createTable(tableName, table => {
table.increments('id');
table.string('name');
table.datetime('time', { useTz: false });
@ -16,7 +15,8 @@ const createStorage = (opts = {}) => {
return {
async logMigration(migrationName) {
await knex
await db
.getConnection()
.insert({
name: migrationName,
time: new Date(),
@ -25,7 +25,8 @@ const createStorage = (opts = {}) => {
},
async unlogMigration(migrationName) {
await knex(tableName)
await db
.getConnection(tableName)
.del()
.where({ name: migrationName });
},
@ -36,7 +37,8 @@ const createStorage = (opts = {}) => {
return [];
}
const logs = await knex
const logs = await db
.getConnection(tableName)
.select()
.from(tableName)
.orderBy('time');

View File

@ -67,7 +67,7 @@ const applyJoin = (qb, join) => {
orderBy,
} = join;
qb[method]({ [alias]: referencedTable }, inner => {
qb[method](`${referencedTable} as ${alias}`, inner => {
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
if (on) {

View File

@ -205,10 +205,13 @@ const createQueryBuilder = (uid, db) => {
this.select('id');
const subQB = this.getKnexQuery();
const nestedSubQuery = db.connection.select('id').from(subQB.as('subQuery'));
const nestedSubQuery = db
.getConnection()
.select('id')
.from(subQB.as('subQuery'));
return db
.connection(tableName)
.getConnection(tableName)
[state.type]()
.whereIn('id', nestedSubQuery);
},
@ -257,9 +260,9 @@ const createQueryBuilder = (uid, db) => {
this.select('*');
}
const aliasedTableName = this.mustUseAlias() ? { [this.alias]: tableName } : tableName;
const aliasedTableName = this.mustUseAlias() ? `${tableName} as ${this.alias}` : tableName;
const qb = db.connection(aliasedTableName);
const qb = db.getConnection(aliasedTableName);
if (this.shouldUseSubQuery()) {
return this.runSubQuery();

View File

@ -11,8 +11,8 @@ module.exports = db => {
* Returns a knex schema builder instance
* @param {string} table - table name
*/
getSchemaBuilder(table, trx = db.connection) {
return table.schema ? trx.schema.withSchema(table.schema) : trx.schema;
getSchemaBuilder(trx) {
return db.getSchemaConnection(trx);
},
/**
@ -20,10 +20,7 @@ module.exports = db => {
* @param {Schema} schema - database schema
*/
async createSchema(schema) {
// TODO: ensure database exists;
await db.connection.transaction(async trx => {
// create tables without FKs first do avoid ordering issues
await this.createTables(schema.tables, trx);
});
},
@ -36,14 +33,14 @@ module.exports = db => {
async createTables(tables, trx) {
for (const table of tables) {
debug(`Creating table: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.createTable(schemaBuilder, table);
}
// create FKs once all the tables exist
for (const table of tables) {
debug(`Creating table foreign keys: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.createTableForeignKeys(schemaBuilder, table);
}
},
@ -61,7 +58,7 @@ module.exports = db => {
await db.connection.transaction(async trx => {
for (const table of schema.tables.reverse()) {
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.dropTable(schemaBuilder, table);
}
});
@ -73,29 +70,33 @@ module.exports = db => {
*/
// TODO: implement force option to disable removal in DB
async updateSchema(schemaDiff) {
const { forceMigration } = db.config.settings;
await db.dialect.startSchemaUpdate();
await db.connection.transaction(async trx => {
await this.createTables(schemaDiff.tables.added, trx);
if (forceMigration) {
// drop all delete table foreign keys then delete the tables
for (const table of schemaDiff.tables.removed) {
debug(`Removing table foreign keys: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.dropTableForeignKeys(schemaBuilder, table);
}
for (const table of schemaDiff.tables.removed) {
debug(`Removing table: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.dropTable(schemaBuilder, table);
}
}
for (const table of schemaDiff.tables.updated) {
debug(`Updating table: ${table.name}`);
// alter table
const schemaBuilder = this.getSchemaBuilder(table, trx);
const schemaBuilder = this.getSchemaBuilder(trx);
await helpers.alterTable(schemaBuilder, table);
}
@ -118,7 +119,11 @@ const createHelpers = db => {
const constraint = tableBuilder
.foreign(columns, name)
.references(referencedColumns)
.inTable(referencedTable);
.inTable(
db.connection.getSchemaName()
? `${db.connection.getSchemaName()}.${referencedTable}`
: referencedTable
);
if (onDelete) {
constraint.onDelete(onDelete);
@ -167,6 +172,10 @@ const createHelpers = db => {
* @param {Index} index
*/
const dropIndex = (tableBuilder, index) => {
if (!db.config.settings.forceMigration) {
return;
}
const { type, columns, name } = index;
switch (type) {
@ -221,7 +230,11 @@ const createHelpers = db => {
* @param {Column} column
*/
const dropColumn = (tableBuilder, column) => {
tableBuilder.dropColumn(column.name);
if (!db.config.settings.forceMigration) {
return;
}
return tableBuilder.dropColumn(column.name);
};
/**
@ -316,7 +329,13 @@ const createHelpers = db => {
* @param {Knex.SchemaBuilder} schemaBuilder
* @param {Table} table
*/
const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
const dropTable = (schemaBuilder, table) => {
if (!db.config.settings.forceMigration) {
return;
}
return schemaBuilder.dropTableIfExists(table.name);
};
/**
* Creates a table foreign keys constraints
@ -336,6 +355,10 @@ const createHelpers = db => {
* @param {Table} table
*/
const dropTableForeignKeys = async (schemaBuilder, table) => {
if (!db.config.settings.forceMigration) {
return;
}
// foreign keys
await schemaBuilder.table(table.name, tableBuilder => {
(table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));

View File

@ -5,10 +5,10 @@ const crypto = require('crypto');
const TABLE_NAME = 'strapi_database_schema';
module.exports = db => {
const hasSchemaTable = () => db.connection.schema.hasTable(TABLE_NAME);
const hasSchemaTable = () => db.getSchemaConnection().hasTable(TABLE_NAME);
const createSchemaTable = () => {
return db.connection.schema.createTable(TABLE_NAME, t => {
return db.getSchemaConnection().createTable(TABLE_NAME, t => {
t.increments('id');
t.json('schema');
t.datetime('time', { useTz: false });
@ -26,7 +26,8 @@ module.exports = db => {
async read() {
await checkTableExists();
const res = await db.connection
const res = await db
.getConnection()
.select('*')
.from(TABLE_NAME)
.orderBy('time', 'DESC')
@ -55,21 +56,24 @@ module.exports = db => {
await checkTableExists();
// NOTE: we can remove this to add history
await db.connection(TABLE_NAME).delete();
await db.getConnection(TABLE_NAME).delete();
const time = new Date();
await db.connection(TABLE_NAME).insert({
await db
.getConnection()
.insert({
schema: JSON.stringify(schema),
hash: this.hashSchema(schema),
time,
});
})
.into(TABLE_NAME);
},
async clear() {
await checkTableExists();
await db.connection(TABLE_NAME).truncate();
await db.getConnection(TABLE_NAME).truncate();
},
};
};

View File

@ -366,7 +366,7 @@ class Strapi {
this.telemetry.bootstrap();
let oldContentTypes;
if (await this.db.connection.schema.hasTable(coreStoreModel.collectionName)) {
if (await this.db.getSchemaConnection().hasTable(coreStoreModel.collectionName)) {
oldContentTypes = await this.store.get({
type: 'strapi',
name: 'content_types',