211 lines
6.6 KiB
JavaScript
211 lines
6.6 KiB
JavaScript
const knex = require('knex'),
|
|
omit = require('lodash/omit'),
|
|
debug = require('debug')('knex-migrator:database'),
|
|
errors = require('./errors');
|
|
const DatabaseInfo = require('@tryghost/database-info');
|
|
const {sequence} = require('@tryghost/promise');
|
|
|
|
/**
|
|
* @NOTE: Knex-migrator only supports knex query builder.
|
|
*
|
|
* @param options
|
|
* @returns {Knex.QueryBuilder | Knex}
|
|
*/
|
|
exports.connect = function connect(options) {
|
|
options = options || {};
|
|
|
|
// Alias `mysql` to `mysql2` so we can maintain backwards compatibility
|
|
if (options.client === 'mysql') {
|
|
options.client = 'mysql2';
|
|
}
|
|
|
|
const client = options.client;
|
|
|
|
if (client === 'sqlite3') {
|
|
options.useNullAsDefault = options.useNullAsDefault || false;
|
|
}
|
|
|
|
if (client === 'mysql2') {
|
|
options.connection.timezone = options.connection.timezone || 'Z';
|
|
options.connection.charset = options.connection.charset || 'utf8mb4';
|
|
options.connection.decimalNumbers = true;
|
|
|
|
delete options.connection.filename;
|
|
}
|
|
|
|
return knex(options);
|
|
};
|
|
|
|
/**
|
|
* If you instantiate knex, you won't know if the connection works.
|
|
* This helper functions is used to test the connection. It's basically a "test query".
|
|
*
|
|
* @param connection
|
|
* @returns {Promise<R> | Promise<any> | Promise<T>}
|
|
*/
|
|
exports.ensureConnectionWorks = (connection) => {
|
|
return connection.raw('SELECT 1+1 as RESULT;')
|
|
.catch((err) => {
|
|
if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
|
|
throw new errors.DatabaseError({
|
|
message: 'Invalid database host.',
|
|
help: 'Please double check your database config.',
|
|
err: err
|
|
});
|
|
}
|
|
|
|
throw new errors.DatabaseError({
|
|
message: err.message,
|
|
help: 'Unknown database error',
|
|
err: err
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @description Helper to create a transaction.
|
|
* @param callback
|
|
* @returns {*}
|
|
*/
|
|
module.exports.createTransaction = function (connection, callback) {
|
|
return connection.transaction(callback);
|
|
};
|
|
|
|
/**
|
|
* @description Helper to create the migration table.
|
|
*
|
|
* @TODO: https://github.com/TryGhost/knex-migrator/issues/118
|
|
* @TODO: https://github.com/TryGhost/knex-migrator/issues/91
|
|
* @returns {Promise<R> | Promise<any> | * | Promise<T>}
|
|
*/
|
|
exports.createMigrationsTable = async function createMigrationsTable(connection) {
|
|
const hasTable = await connection.schema.hasTable('migrations');
|
|
if (hasTable) {
|
|
return;
|
|
}
|
|
|
|
// CASE: table does not exist
|
|
debug('Creating table: migrations');
|
|
|
|
await connection.schema.createTable('migrations', function (table) {
|
|
table.increments().primary();
|
|
table.string('name');
|
|
table.string('version');
|
|
table.string('currentVersion');
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Knex-migrator has an inbuilt feature to create a database if it does not exist yet.
|
|
*
|
|
* @param dbConfig
|
|
* @returns {*}
|
|
*/
|
|
exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
|
|
const name = dbConfig.connection.database,
|
|
charset = dbConfig.connection.charset || 'utf8mb4';
|
|
|
|
// @NOTE: Skip, because sqlite3 is a file based database.
|
|
if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
|
|
return Promise.resolve();
|
|
} else if (!DatabaseInfo.isMySQLConfig(dbConfig)) {
|
|
return Promise.reject(new errors.KnexMigrateError({
|
|
message: 'Database is not supported.'
|
|
}));
|
|
}
|
|
|
|
const connection = exports.connect({
|
|
client: dbConfig.client,
|
|
connection: omit(dbConfig.connection, ['database'])
|
|
});
|
|
|
|
debug('Create database', name);
|
|
|
|
return exports.ensureConnectionWorks(connection)
|
|
.then(function () {
|
|
return connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ';');
|
|
})
|
|
.catch(function (err) {
|
|
// CASE: DB exists
|
|
if (err.errno === 1007) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
throw new errors.DatabaseError({
|
|
message: err.message,
|
|
err: err,
|
|
code: 'DATABASE_CREATION_FAILED'
|
|
});
|
|
})
|
|
.finally(function () {
|
|
return new Promise(function (resolve, reject) {
|
|
connection.destroy(function (err) {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
|
|
debug('Destroy connection');
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Drops a database. Is called when you call `knex-migrator reset`.
|
|
*
|
|
* @param options
|
|
* @returns {*}
|
|
*/
|
|
exports.drop = function drop(options) {
|
|
options = options || {};
|
|
|
|
const connection = options.connection,
|
|
dbConfig = options.dbConfig;
|
|
|
|
if (DatabaseInfo.isMySQL(connection)) {
|
|
debug('Drop database: ' + dbConfig.connection.database);
|
|
|
|
return connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;')
|
|
.catch(function (err) {
|
|
// CASE: database does not exist, skip
|
|
if (err.errno === 1049) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.reject(new errors.KnexMigrateError({
|
|
err: err
|
|
}));
|
|
});
|
|
} else if (DatabaseInfo.isSQLite(connection)) {
|
|
// @NOTE: sqlite3 does not support "DROP DATABASE". We have to drop each table instead.
|
|
// @NOTE: We cannot just remove the sqlite3 file, because any database connection will get invalid.
|
|
return connection.raw('SELECT name FROM sqlite_master WHERE type="table";')
|
|
.then(function (tables) {
|
|
return sequence(tables.map(table => () => {
|
|
if (table.name === 'sqlite_sequence') {
|
|
debug('Skip drop table: ' + table.name);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
debug('Drop table: ' + table.name);
|
|
return connection.schema.dropTableIfExists(table.name);
|
|
}));
|
|
})
|
|
.catch(function (err) {
|
|
// CASE: database file was never initialised
|
|
if (err.errno === 10) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.reject(new errors.KnexMigrateError({
|
|
err: err
|
|
}));
|
|
});
|
|
} else {
|
|
return Promise.reject(new errors.KnexMigrateError({
|
|
message: 'Database client not supported: ' + dbConfig.client
|
|
}));
|
|
}
|
|
};
|