mirror of
https://github.com/strapi/strapi.git
synced 2025-09-23 15:29:27 +00:00
Merge pull request #11557 from strapi/v4/database-improvements
Support schema option and add forceMigration options
This commit is contained in:
commit
3b11037883
@ -123,10 +123,6 @@ class MysqlSchemaInspector {
|
|||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDatabaseSchema() {
|
|
||||||
return this.db.connection.client.connectionSettings.schema || 'public';
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTables() {
|
async getTables() {
|
||||||
const [rows] = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
|
const [rows] = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class PostgresqlSchemaInspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDatabaseSchema() {
|
getDatabaseSchema() {
|
||||||
return this.db.connection.client.connectionSettings.schema || 'public';
|
return this.db.connection.getSchemaName() || 'public';
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTables() {
|
async getTables() {
|
||||||
|
@ -13,16 +13,32 @@ const errors = require('./errors');
|
|||||||
// TODO: move back into strapi
|
// TODO: move back into strapi
|
||||||
const { transformContentTypes } = require('./utils/content-types');
|
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 {
|
class Database {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.metadata = createMetadata(config.models);
|
this.metadata = createMetadata(config.models);
|
||||||
|
|
||||||
this.config = config;
|
this.config = {
|
||||||
|
connection: {},
|
||||||
|
settings: {
|
||||||
|
forceMigration: true,
|
||||||
|
},
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
this.dialect = getDialect(this);
|
this.dialect = getDialect(this);
|
||||||
this.dialect.configure();
|
this.dialect.configure();
|
||||||
|
|
||||||
this.connection = knex(this.config.connection);
|
this.connection = createConnection(this.config.connection);
|
||||||
|
|
||||||
this.dialect.initialize();
|
this.dialect.initialize();
|
||||||
|
|
||||||
@ -42,6 +58,17 @@ class Database {
|
|||||||
return this.entityManager.getRepository(uid);
|
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) {
|
queryBuilder(uid) {
|
||||||
return this.entityManager.createQueryBuilder(uid);
|
return this.entityManager.createQueryBuilder(uid);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ const createUmzugProvider = db => {
|
|||||||
|
|
||||||
fse.ensureDirSync(migrationDir);
|
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' });
|
const storage = createStorage({ db, tableName: 'strapi_migrations' });
|
||||||
|
|
||||||
return new Umzug({
|
return new Umzug({
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const createStorage = (opts = {}) => {
|
const createStorage = (opts = {}) => {
|
||||||
const tableName = opts.tableName || 'strapi_migrations';
|
const { db, tableName = 'strapi_migrations' } = opts;
|
||||||
const knex = opts.db.connection;
|
|
||||||
|
|
||||||
const hasMigrationTable = () => knex.schema.hasTable(tableName);
|
const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
|
||||||
|
|
||||||
const createMigrationTable = () => {
|
const createMigrationTable = () => {
|
||||||
return knex.schema.createTable(tableName, table => {
|
return db.getSchemaConnection().createTable(tableName, table => {
|
||||||
table.increments('id');
|
table.increments('id');
|
||||||
table.string('name');
|
table.string('name');
|
||||||
table.datetime('time', { useTz: false });
|
table.datetime('time', { useTz: false });
|
||||||
@ -16,7 +15,8 @@ const createStorage = (opts = {}) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
async logMigration(migrationName) {
|
async logMigration(migrationName) {
|
||||||
await knex
|
await db
|
||||||
|
.getConnection()
|
||||||
.insert({
|
.insert({
|
||||||
name: migrationName,
|
name: migrationName,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
@ -25,7 +25,8 @@ const createStorage = (opts = {}) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async unlogMigration(migrationName) {
|
async unlogMigration(migrationName) {
|
||||||
await knex(tableName)
|
await db
|
||||||
|
.getConnection(tableName)
|
||||||
.del()
|
.del()
|
||||||
.where({ name: migrationName });
|
.where({ name: migrationName });
|
||||||
},
|
},
|
||||||
@ -36,7 +37,8 @@ const createStorage = (opts = {}) => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const logs = await knex
|
const logs = await db
|
||||||
|
.getConnection(tableName)
|
||||||
.select()
|
.select()
|
||||||
.from(tableName)
|
.from(tableName)
|
||||||
.orderBy('time');
|
.orderBy('time');
|
||||||
|
@ -67,7 +67,7 @@ const applyJoin = (qb, join) => {
|
|||||||
orderBy,
|
orderBy,
|
||||||
} = join;
|
} = join;
|
||||||
|
|
||||||
qb[method]({ [alias]: referencedTable }, inner => {
|
qb[method](`${referencedTable} as ${alias}`, inner => {
|
||||||
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
||||||
|
|
||||||
if (on) {
|
if (on) {
|
||||||
|
@ -205,10 +205,13 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
this.select('id');
|
this.select('id');
|
||||||
const subQB = this.getKnexQuery();
|
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
|
return db
|
||||||
.connection(tableName)
|
.getConnection(tableName)
|
||||||
[state.type]()
|
[state.type]()
|
||||||
.whereIn('id', nestedSubQuery);
|
.whereIn('id', nestedSubQuery);
|
||||||
},
|
},
|
||||||
@ -257,9 +260,9 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
this.select('*');
|
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()) {
|
if (this.shouldUseSubQuery()) {
|
||||||
return this.runSubQuery();
|
return this.runSubQuery();
|
||||||
|
@ -11,8 +11,8 @@ module.exports = db => {
|
|||||||
* Returns a knex schema builder instance
|
* Returns a knex schema builder instance
|
||||||
* @param {string} table - table name
|
* @param {string} table - table name
|
||||||
*/
|
*/
|
||||||
getSchemaBuilder(table, trx = db.connection) {
|
getSchemaBuilder(trx) {
|
||||||
return table.schema ? trx.schema.withSchema(table.schema) : trx.schema;
|
return db.getSchemaConnection(trx);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,10 +20,7 @@ module.exports = db => {
|
|||||||
* @param {Schema} schema - database schema
|
* @param {Schema} schema - database schema
|
||||||
*/
|
*/
|
||||||
async createSchema(schema) {
|
async createSchema(schema) {
|
||||||
// TODO: ensure database exists;
|
|
||||||
|
|
||||||
await db.connection.transaction(async trx => {
|
await db.connection.transaction(async trx => {
|
||||||
// create tables without FKs first do avoid ordering issues
|
|
||||||
await this.createTables(schema.tables, trx);
|
await this.createTables(schema.tables, trx);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -36,14 +33,14 @@ module.exports = db => {
|
|||||||
async createTables(tables, trx) {
|
async createTables(tables, trx) {
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
debug(`Creating table: ${table.name}`);
|
debug(`Creating table: ${table.name}`);
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
await helpers.createTable(schemaBuilder, table);
|
await helpers.createTable(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create FKs once all the tables exist
|
// create FKs once all the tables exist
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
debug(`Creating table foreign keys: ${table.name}`);
|
debug(`Creating table foreign keys: ${table.name}`);
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
await helpers.createTableForeignKeys(schemaBuilder, table);
|
await helpers.createTableForeignKeys(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -61,7 +58,7 @@ module.exports = db => {
|
|||||||
|
|
||||||
await db.connection.transaction(async trx => {
|
await db.connection.transaction(async trx => {
|
||||||
for (const table of schema.tables.reverse()) {
|
for (const table of schema.tables.reverse()) {
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
await helpers.dropTable(schemaBuilder, table);
|
await helpers.dropTable(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -73,29 +70,33 @@ module.exports = db => {
|
|||||||
*/
|
*/
|
||||||
// TODO: implement force option to disable removal in DB
|
// TODO: implement force option to disable removal in DB
|
||||||
async updateSchema(schemaDiff) {
|
async updateSchema(schemaDiff) {
|
||||||
|
const { forceMigration } = db.config.settings;
|
||||||
|
|
||||||
await db.dialect.startSchemaUpdate();
|
await db.dialect.startSchemaUpdate();
|
||||||
await db.connection.transaction(async trx => {
|
await db.connection.transaction(async trx => {
|
||||||
await this.createTables(schemaDiff.tables.added, trx);
|
await this.createTables(schemaDiff.tables.added, trx);
|
||||||
|
|
||||||
|
if (forceMigration) {
|
||||||
// drop all delete table foreign keys then delete the tables
|
// drop all delete table foreign keys then delete the tables
|
||||||
for (const table of schemaDiff.tables.removed) {
|
for (const table of schemaDiff.tables.removed) {
|
||||||
debug(`Removing table foreign keys: ${table.name}`);
|
debug(`Removing table foreign keys: ${table.name}`);
|
||||||
|
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
await helpers.dropTableForeignKeys(schemaBuilder, table);
|
await helpers.dropTableForeignKeys(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const table of schemaDiff.tables.removed) {
|
for (const table of schemaDiff.tables.removed) {
|
||||||
debug(`Removing table: ${table.name}`);
|
debug(`Removing table: ${table.name}`);
|
||||||
|
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
await helpers.dropTable(schemaBuilder, table);
|
await helpers.dropTable(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const table of schemaDiff.tables.updated) {
|
for (const table of schemaDiff.tables.updated) {
|
||||||
debug(`Updating table: ${table.name}`);
|
debug(`Updating table: ${table.name}`);
|
||||||
// alter table
|
// alter table
|
||||||
const schemaBuilder = this.getSchemaBuilder(table, trx);
|
const schemaBuilder = this.getSchemaBuilder(trx);
|
||||||
|
|
||||||
await helpers.alterTable(schemaBuilder, table);
|
await helpers.alterTable(schemaBuilder, table);
|
||||||
}
|
}
|
||||||
@ -118,7 +119,11 @@ const createHelpers = db => {
|
|||||||
const constraint = tableBuilder
|
const constraint = tableBuilder
|
||||||
.foreign(columns, name)
|
.foreign(columns, name)
|
||||||
.references(referencedColumns)
|
.references(referencedColumns)
|
||||||
.inTable(referencedTable);
|
.inTable(
|
||||||
|
db.connection.getSchemaName()
|
||||||
|
? `${db.connection.getSchemaName()}.${referencedTable}`
|
||||||
|
: referencedTable
|
||||||
|
);
|
||||||
|
|
||||||
if (onDelete) {
|
if (onDelete) {
|
||||||
constraint.onDelete(onDelete);
|
constraint.onDelete(onDelete);
|
||||||
@ -167,6 +172,10 @@ const createHelpers = db => {
|
|||||||
* @param {Index} index
|
* @param {Index} index
|
||||||
*/
|
*/
|
||||||
const dropIndex = (tableBuilder, index) => {
|
const dropIndex = (tableBuilder, index) => {
|
||||||
|
if (!db.config.settings.forceMigration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { type, columns, name } = index;
|
const { type, columns, name } = index;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -221,7 +230,11 @@ const createHelpers = db => {
|
|||||||
* @param {Column} column
|
* @param {Column} column
|
||||||
*/
|
*/
|
||||||
const dropColumn = (tableBuilder, 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 {Knex.SchemaBuilder} schemaBuilder
|
||||||
* @param {Table} table
|
* @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
|
* Creates a table foreign keys constraints
|
||||||
@ -336,6 +355,10 @@ const createHelpers = db => {
|
|||||||
* @param {Table} table
|
* @param {Table} table
|
||||||
*/
|
*/
|
||||||
const dropTableForeignKeys = async (schemaBuilder, table) => {
|
const dropTableForeignKeys = async (schemaBuilder, table) => {
|
||||||
|
if (!db.config.settings.forceMigration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// foreign keys
|
// foreign keys
|
||||||
await schemaBuilder.table(table.name, tableBuilder => {
|
await schemaBuilder.table(table.name, tableBuilder => {
|
||||||
(table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));
|
(table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));
|
||||||
|
@ -5,10 +5,10 @@ const crypto = require('crypto');
|
|||||||
const TABLE_NAME = 'strapi_database_schema';
|
const TABLE_NAME = 'strapi_database_schema';
|
||||||
|
|
||||||
module.exports = db => {
|
module.exports = db => {
|
||||||
const hasSchemaTable = () => db.connection.schema.hasTable(TABLE_NAME);
|
const hasSchemaTable = () => db.getSchemaConnection().hasTable(TABLE_NAME);
|
||||||
|
|
||||||
const createSchemaTable = () => {
|
const createSchemaTable = () => {
|
||||||
return db.connection.schema.createTable(TABLE_NAME, t => {
|
return db.getSchemaConnection().createTable(TABLE_NAME, t => {
|
||||||
t.increments('id');
|
t.increments('id');
|
||||||
t.json('schema');
|
t.json('schema');
|
||||||
t.datetime('time', { useTz: false });
|
t.datetime('time', { useTz: false });
|
||||||
@ -26,7 +26,8 @@ module.exports = db => {
|
|||||||
async read() {
|
async read() {
|
||||||
await checkTableExists();
|
await checkTableExists();
|
||||||
|
|
||||||
const res = await db.connection
|
const res = await db
|
||||||
|
.getConnection()
|
||||||
.select('*')
|
.select('*')
|
||||||
.from(TABLE_NAME)
|
.from(TABLE_NAME)
|
||||||
.orderBy('time', 'DESC')
|
.orderBy('time', 'DESC')
|
||||||
@ -55,21 +56,24 @@ module.exports = db => {
|
|||||||
await checkTableExists();
|
await checkTableExists();
|
||||||
|
|
||||||
// NOTE: we can remove this to add history
|
// NOTE: we can remove this to add history
|
||||||
await db.connection(TABLE_NAME).delete();
|
await db.getConnection(TABLE_NAME).delete();
|
||||||
|
|
||||||
const time = new Date();
|
const time = new Date();
|
||||||
|
|
||||||
await db.connection(TABLE_NAME).insert({
|
await db
|
||||||
|
.getConnection()
|
||||||
|
.insert({
|
||||||
schema: JSON.stringify(schema),
|
schema: JSON.stringify(schema),
|
||||||
hash: this.hashSchema(schema),
|
hash: this.hashSchema(schema),
|
||||||
time,
|
time,
|
||||||
});
|
})
|
||||||
|
.into(TABLE_NAME);
|
||||||
},
|
},
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
await checkTableExists();
|
await checkTableExists();
|
||||||
|
|
||||||
await db.connection(TABLE_NAME).truncate();
|
await db.getConnection(TABLE_NAME).truncate();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -366,7 +366,7 @@ class Strapi {
|
|||||||
this.telemetry.bootstrap();
|
this.telemetry.bootstrap();
|
||||||
|
|
||||||
let oldContentTypes;
|
let oldContentTypes;
|
||||||
if (await this.db.connection.schema.hasTable(coreStoreModel.collectionName)) {
|
if (await this.db.getSchemaConnection().hasTable(coreStoreModel.collectionName)) {
|
||||||
oldContentTypes = await this.store.get({
|
oldContentTypes = await this.store.get({
|
||||||
type: 'strapi',
|
type: 'strapi',
|
||||||
name: 'content_types',
|
name: 'content_types',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user