diff --git a/packages/core/database/lib/dialects/mysql/schema-inspector.js b/packages/core/database/lib/dialects/mysql/schema-inspector.js index eaa6f8c975..0588aeeced 100644 --- a/packages/core/database/lib/dialects/mysql/schema-inspector.js +++ b/packages/core/database/lib/dialects/mysql/schema-inspector.js @@ -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); diff --git a/packages/core/database/lib/dialects/postgresql/schema-inspector.js b/packages/core/database/lib/dialects/postgresql/schema-inspector.js index 72e4abcc12..7e13a56e4d 100644 --- a/packages/core/database/lib/dialects/postgresql/schema-inspector.js +++ b/packages/core/database/lib/dialects/postgresql/schema-inspector.js @@ -139,7 +139,7 @@ class PostgresqlSchemaInspector { } getDatabaseSchema() { - return this.db.connection.client.connectionSettings.schema || 'public'; + return this.db.connection.getSchemaName() || 'public'; } async getTables() { diff --git a/packages/core/database/lib/index.js b/packages/core/database/lib/index.js index c560bde539..d75d9ad177 100644 --- a/packages/core/database/lib/index.js +++ b/packages/core/database/lib/index.js @@ -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); } diff --git a/packages/core/database/lib/migrations/index.js b/packages/core/database/lib/migrations/index.js index ab18af0078..7fd50c2eef 100644 --- a/packages/core/database/lib/migrations/index.js +++ b/packages/core/database/lib/migrations/index.js @@ -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({ diff --git a/packages/core/database/lib/migrations/storage.js b/packages/core/database/lib/migrations/storage.js index 285ee3e21d..3b2e154aec 100644 --- a/packages/core/database/lib/migrations/storage.js +++ b/packages/core/database/lib/migrations/storage.js @@ -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'); diff --git a/packages/core/database/lib/query/helpers/join.js b/packages/core/database/lib/query/helpers/join.js index d31fab8ba5..c46cd5133e 100644 --- a/packages/core/database/lib/query/helpers/join.js +++ b/packages/core/database/lib/query/helpers/join.js @@ -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) { diff --git a/packages/core/database/lib/query/query-builder.js b/packages/core/database/lib/query/query-builder.js index b559a0a378..de39d962c7 100644 --- a/packages/core/database/lib/query/query-builder.js +++ b/packages/core/database/lib/query/query-builder.js @@ -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(); diff --git a/packages/core/database/lib/schema/builder.js b/packages/core/database/lib/schema/builder.js index 5bb026fc5a..05d1a07291 100644 --- a/packages/core/database/lib/schema/builder.js +++ b/packages/core/database/lib/schema/builder.js @@ -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); - // drop all delete table foreign keys then delete the tables - for (const table of schemaDiff.tables.removed) { - debug(`Removing table foreign keys: ${table.name}`); + 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); - await helpers.dropTableForeignKeys(schemaBuilder, table); - } + const schemaBuilder = this.getSchemaBuilder(trx); + await helpers.dropTableForeignKeys(schemaBuilder, table); + } - for (const table of schemaDiff.tables.removed) { - debug(`Removing table: ${table.name}`); + for (const table of schemaDiff.tables.removed) { + debug(`Removing table: ${table.name}`); - const schemaBuilder = this.getSchemaBuilder(table, trx); - await helpers.dropTable(schemaBuilder, table); + 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)); diff --git a/packages/core/database/lib/schema/storage.js b/packages/core/database/lib/schema/storage.js index 7b53b5fcd3..158c5f74f4 100644 --- a/packages/core/database/lib/schema/storage.js +++ b/packages/core/database/lib/schema/storage.js @@ -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({ - schema: JSON.stringify(schema), - hash: this.hashSchema(schema), - time, - }); + 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(); }, }; }; diff --git a/packages/core/strapi/lib/Strapi.js b/packages/core/strapi/lib/Strapi.js index 1cc32fcbe2..3cde48ac84 100644 --- a/packages/core/strapi/lib/Strapi.js +++ b/packages/core/strapi/lib/Strapi.js @@ -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',