2020-09-17 17:58:12 +02:00
|
|
|
'use strict';
|
|
|
|
import mysql from 'mysql';
|
|
|
|
import { AntaresCore } from '../AntaresCore';
|
|
|
|
|
|
|
|
export class MySQLClient extends AntaresCore {
|
|
|
|
/**
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async connect () {
|
|
|
|
if (!this._poolSize)
|
|
|
|
this._connection = mysql.createConnection(this._params);
|
|
|
|
else
|
|
|
|
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
destroy () {
|
|
|
|
this._connection.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes an USE query
|
|
|
|
*
|
2020-09-27 19:06:13 +02:00
|
|
|
* @param {String} schema
|
2020-09-17 17:58:12 +02:00
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
use (schema) {
|
2020-09-29 16:43:20 +02:00
|
|
|
return this.raw(`USE \`${schema}\``);
|
2020-09-17 17:58:12 +02:00
|
|
|
}
|
|
|
|
|
2020-09-27 19:06:13 +02:00
|
|
|
/**
|
|
|
|
* @returns {Array.<Object>} databases scructure
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getStructure () {
|
|
|
|
const { rows: databases } = await this.raw('SHOW DATABASES');
|
2020-10-03 12:11:42 +02:00
|
|
|
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
|
|
|
|
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
|
|
|
|
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
|
|
|
|
|
2020-12-28 13:05:30 +01:00
|
|
|
const tablesArr = [];
|
2020-10-03 12:11:42 +02:00
|
|
|
const triggersArr = [];
|
2020-12-28 13:05:30 +01:00
|
|
|
|
2020-10-03 12:11:42 +02:00
|
|
|
for (const db of databases) {
|
2020-12-28 13:05:30 +01:00
|
|
|
let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``);
|
|
|
|
if (tables.length) {
|
|
|
|
tables = tables.map(table => {
|
|
|
|
table.Db = db.Database;
|
|
|
|
return table;
|
|
|
|
});
|
|
|
|
tablesArr.push(...tables);
|
|
|
|
}
|
|
|
|
|
2020-10-03 12:11:42 +02:00
|
|
|
let { rows: triggers } = await this.raw(`SHOW TRIGGERS FROM \`${db.Database}\``);
|
|
|
|
if (triggers.length) {
|
|
|
|
triggers = triggers.map(trigger => {
|
|
|
|
trigger.Db = db.Database;
|
|
|
|
return trigger;
|
|
|
|
});
|
|
|
|
triggersArr.push(...triggers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-14 19:00:13 +02:00
|
|
|
return databases.map(db => {
|
|
|
|
// TABLES
|
2020-12-28 13:05:30 +01:00
|
|
|
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
|
2020-10-12 18:45:15 +02:00
|
|
|
let tableType;
|
2020-12-28 13:05:30 +01:00
|
|
|
switch (table.Comment) {
|
2020-10-12 18:45:15 +02:00
|
|
|
case 'VIEW':
|
|
|
|
tableType = 'view';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
tableType = 'table';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2020-12-28 13:05:30 +01:00
|
|
|
name: table.Name,
|
2020-10-12 18:45:15 +02:00
|
|
|
type: tableType,
|
2020-12-28 13:05:30 +01:00
|
|
|
rows: table.Rows,
|
|
|
|
created: table.Create_time,
|
|
|
|
updated: table.Update_time,
|
|
|
|
engine: table.Engine,
|
|
|
|
comment: table.Comment,
|
|
|
|
size: table.Data_length + table.Index_length,
|
|
|
|
autoIncrement: table.Auto_increment,
|
|
|
|
collation: table.Collation
|
2020-10-12 18:45:15 +02:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2020-10-14 19:00:13 +02:00
|
|
|
// PROCEDURES
|
|
|
|
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
|
|
|
|
return {
|
|
|
|
name: procedure.Name,
|
|
|
|
type: procedure.Type,
|
|
|
|
definer: procedure.Definer,
|
|
|
|
created: procedure.Created,
|
|
|
|
updated: procedure.Modified,
|
|
|
|
comment: procedure.Comment,
|
|
|
|
charset: procedure.character_set_client,
|
|
|
|
security: procedure.Security_type
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// SCHEDULERS
|
|
|
|
const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
|
|
|
|
return {
|
|
|
|
name: scheduler.EVENT_NAME,
|
|
|
|
definition: scheduler.EVENT_DEFINITION,
|
|
|
|
type: scheduler.EVENT_TYPE,
|
|
|
|
definer: scheduler.DEFINER,
|
|
|
|
body: scheduler.EVENT_BODY,
|
|
|
|
starts: scheduler.STARTS,
|
|
|
|
ends: scheduler.ENDS,
|
|
|
|
status: scheduler.STATUS,
|
|
|
|
executeAt: scheduler.EXECUTE_AT,
|
|
|
|
intervalField: scheduler.INTERVAL_FIELD,
|
|
|
|
intervalValue: scheduler.INTERVAL_VALUE,
|
|
|
|
onCompletion: scheduler.ON_COMPLETION,
|
|
|
|
originator: scheduler.ORIGINATOR,
|
|
|
|
sqlMode: scheduler.SQL_MODE,
|
|
|
|
created: scheduler.CREATED,
|
|
|
|
updated: scheduler.LAST_ALTERED,
|
|
|
|
lastExecuted: scheduler.LAST_EXECUTED,
|
|
|
|
comment: scheduler.EVENT_COMMENT,
|
|
|
|
charset: scheduler.CHARACTER_SET_CLIENT,
|
|
|
|
timezone: scheduler.TIME_ZONE
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// TRIGGERS
|
|
|
|
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
|
|
|
|
return {
|
|
|
|
name: trigger.Trigger,
|
|
|
|
statement: trigger.Statement,
|
|
|
|
timing: trigger.Timing,
|
|
|
|
definer: trigger.Definer,
|
|
|
|
event: trigger.Event,
|
|
|
|
table: trigger.Table,
|
|
|
|
sqlMode: trigger.sql_mode,
|
|
|
|
created: trigger.Created,
|
|
|
|
charset: trigger.character_set_client
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2020-09-27 19:06:13 +02:00
|
|
|
return {
|
|
|
|
name: db.Database,
|
2020-10-14 19:00:13 +02:00
|
|
|
tables: remappedTables,
|
|
|
|
functions: functions.filter(func => func.Db === db.Database), // TODO: remap functions
|
|
|
|
procedures: remappedProcedures,
|
|
|
|
triggers: remappedTriggers,
|
|
|
|
schedulers: remappedSchedulers
|
2020-09-27 19:06:13 +02:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-16 17:26:47 +02:00
|
|
|
/**
|
|
|
|
* @param {Object} params
|
|
|
|
* @param {String} params.schema
|
|
|
|
* @param {String} params.table
|
|
|
|
* @returns {Object} table scructure
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getTableColumns ({ schema, table }) {
|
|
|
|
const { rows } = await this
|
|
|
|
.select('*')
|
|
|
|
.schema('information_schema')
|
|
|
|
.from('COLUMNS')
|
|
|
|
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
|
|
|
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
|
|
|
.run();
|
|
|
|
|
|
|
|
return rows.map(field => {
|
2020-10-17 10:12:40 +02:00
|
|
|
let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
|
|
|
|
numLength = numLength ? +numLength.pop() : null;
|
|
|
|
|
2020-10-16 17:26:47 +02:00
|
|
|
return {
|
|
|
|
name: field.COLUMN_NAME,
|
|
|
|
key: field.COLUMN_KEY.toLowerCase(),
|
2020-12-07 19:11:29 +01:00
|
|
|
type: field.DATA_TYPE.toUpperCase(),
|
2020-10-16 17:26:47 +02:00
|
|
|
schema: field.TABLE_SCHEMA,
|
|
|
|
table: field.TABLE_NAME,
|
|
|
|
numPrecision: field.NUMERIC_PRECISION,
|
2020-10-17 10:12:40 +02:00
|
|
|
numLength,
|
2020-10-16 17:26:47 +02:00
|
|
|
datePrecision: field.DATETIME_PRECISION,
|
|
|
|
charLength: field.CHARACTER_MAXIMUM_LENGTH,
|
2020-10-17 10:12:40 +02:00
|
|
|
nullable: field.IS_NULLABLE.includes('YES'),
|
|
|
|
unsigned: field.COLUMN_TYPE.includes('unsigned'),
|
|
|
|
zerofill: field.COLUMN_TYPE.includes('zerofill'),
|
|
|
|
order: field.ORDINAL_POSITION,
|
2020-10-16 17:26:47 +02:00
|
|
|
default: field.COLUMN_DEFAULT,
|
|
|
|
charset: field.CHARACTER_SET_NAME,
|
|
|
|
collation: field.COLLATION_NAME,
|
|
|
|
autoIncrement: field.EXTRA.includes('auto_increment'),
|
2020-11-13 12:39:40 +01:00
|
|
|
onUpdate: field.EXTRA.toLowerCase().includes('on update') ? field.EXTRA.replace('on update', '') : '',
|
2020-10-16 17:26:47 +02:00
|
|
|
comment: field.COLUMN_COMMENT
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-20 17:24:02 +01:00
|
|
|
/**
|
|
|
|
* @param {Object} params
|
|
|
|
* @param {String} params.schema
|
|
|
|
* @param {String} params.table
|
|
|
|
* @returns {Object} table indexes
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getTableIndexes ({ schema, table }) {
|
|
|
|
const { rows } = await this.raw(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``);
|
|
|
|
|
|
|
|
return rows.map(row => {
|
|
|
|
return {
|
|
|
|
unique: !row.Non_unique,
|
|
|
|
name: row.Key_name,
|
|
|
|
column: row.Column_name,
|
|
|
|
indexType: row.Index_type,
|
|
|
|
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !row.Non_unique ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
|
|
|
|
cardinality: row.Cardinality,
|
|
|
|
comment: row.Comment,
|
|
|
|
indexComment: row.Index_comment
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-16 17:26:47 +02:00
|
|
|
/**
|
|
|
|
* @param {Object} params
|
|
|
|
* @param {String} params.schema
|
|
|
|
* @param {String} params.table
|
|
|
|
* @returns {Object} table key usage
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getKeyUsage ({ schema, table }) {
|
|
|
|
const { rows } = await this
|
|
|
|
.select('*')
|
|
|
|
.schema('information_schema')
|
|
|
|
.from('KEY_COLUMN_USAGE')
|
|
|
|
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
|
|
|
.run();
|
|
|
|
|
2020-12-15 17:08:36 +01:00
|
|
|
const { rows: extras } = await this
|
|
|
|
.select('*')
|
|
|
|
.schema('information_schema')
|
|
|
|
.from('REFERENTIAL_CONSTRAINTS')
|
|
|
|
.where({ CONSTRAINT_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
|
|
|
.run();
|
|
|
|
|
2020-10-16 17:26:47 +02:00
|
|
|
return rows.map(field => {
|
2020-12-15 17:08:36 +01:00
|
|
|
const extra = extras.find(x => x.CONSTRAINT_NAME === field.CONSTRAINT_NAME);
|
2020-10-16 17:26:47 +02:00
|
|
|
return {
|
|
|
|
schema: field.TABLE_SCHEMA,
|
|
|
|
table: field.TABLE_NAME,
|
2020-12-15 17:08:36 +01:00
|
|
|
field: field.COLUMN_NAME,
|
2020-10-16 17:26:47 +02:00
|
|
|
position: field.ORDINAL_POSITION,
|
|
|
|
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
|
|
|
constraintName: field.CONSTRAINT_NAME,
|
|
|
|
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
|
|
|
refTable: field.REFERENCED_TABLE_NAME,
|
2020-12-15 17:08:36 +01:00
|
|
|
refField: field.REFERENCED_COLUMN_NAME,
|
|
|
|
onUpdate: extra.UPDATE_RULE,
|
|
|
|
onDelete: extra.DELETE_RULE
|
2020-10-16 17:26:47 +02:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-26 14:47:15 +01:00
|
|
|
/**
|
|
|
|
* SHOW CREATE VIEW
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} view informations
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getViewInformations ({ schema, view }) {
|
|
|
|
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
|
|
|
|
const results = await this.raw(sql);
|
|
|
|
|
|
|
|
return results.rows.map(row => {
|
|
|
|
return {
|
|
|
|
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
|
|
|
|
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
|
|
|
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
|
|
|
|
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
|
|
|
|
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
|
|
|
|
name: row.View
|
|
|
|
};
|
|
|
|
})[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DROP VIEW
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async dropView (params) {
|
|
|
|
const sql = `DROP VIEW \`${params.view}\``;// TODO: schema
|
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-12-26 15:37:34 +01:00
|
|
|
/**
|
|
|
|
* ALTER VIEW
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async alterView (params) {
|
|
|
|
const { view } = params;
|
2020-12-27 13:14:41 +01:00
|
|
|
let sql = `ALTER ALGORITHM = ${view.algorithm} DEFINER=${view.definer} SQL SECURITY ${view.security} VIEW \`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
|
|
|
|
|
|
|
|
if (view.name !== view.oldName)
|
|
|
|
sql += `; RENAME TABLE \`${view.oldName}\` TO \`${view.name}\``;
|
|
|
|
|
2020-12-26 15:37:34 +01:00
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-12-27 16:16:48 +01:00
|
|
|
/**
|
|
|
|
* CREATE VIEW
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async createView (view) {
|
|
|
|
const sql = `CREATE ALGORITHM = ${view.algorithm} ${view.definer ? `DEFINER=${view.definer} ` : ''}SQL SECURITY ${view.security} VIEW \`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
|
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-09-25 12:39:58 +02:00
|
|
|
/**
|
|
|
|
* SHOW COLLATION
|
|
|
|
*
|
2020-09-27 19:06:13 +02:00
|
|
|
* @returns {Array.<Object>} collations list
|
2020-09-25 12:39:58 +02:00
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getCollations () {
|
2020-09-27 19:06:13 +02:00
|
|
|
const results = await this.raw('SHOW COLLATION');
|
2020-09-25 12:39:58 +02:00
|
|
|
|
|
|
|
return results.rows.map(row => {
|
|
|
|
return {
|
|
|
|
charset: row.Charset,
|
|
|
|
collation: row.Collation,
|
|
|
|
compiled: row.Compiled.includes('Yes'),
|
|
|
|
default: row.Default.includes('Yes'),
|
|
|
|
id: row.Id,
|
|
|
|
sortLen: row.Sortlen
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SHOW VARIABLES
|
|
|
|
*
|
2020-09-27 19:06:13 +02:00
|
|
|
* @returns {Array.<Object>} variables list
|
2020-09-25 12:39:58 +02:00
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getVariables () {
|
|
|
|
const sql = 'SHOW VARIABLES';
|
|
|
|
const results = await this.raw(sql);
|
|
|
|
|
|
|
|
return results.rows.map(row => {
|
|
|
|
return {
|
|
|
|
name: row.Variable_name,
|
|
|
|
value: row.Value
|
|
|
|
};
|
|
|
|
});
|
2020-09-24 13:09:01 +02:00
|
|
|
}
|
|
|
|
|
2020-11-16 17:16:39 +01:00
|
|
|
/**
|
|
|
|
* SHOW ENGINES
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} engines list
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async getEngines () {
|
|
|
|
const sql = 'SHOW ENGINES';
|
|
|
|
const results = await this.raw(sql);
|
|
|
|
|
|
|
|
return results.rows.map(row => {
|
|
|
|
return {
|
|
|
|
name: row.Engine,
|
|
|
|
support: row.Support,
|
|
|
|
comment: row.Comment,
|
2020-12-03 13:00:54 +01:00
|
|
|
transactions: row.Transactions,
|
2020-11-16 17:16:39 +01:00
|
|
|
xa: row.XA,
|
2020-12-03 13:00:54 +01:00
|
|
|
savepoints: row.Savepoints,
|
|
|
|
isDefault: row.Support.includes('DEFAULT')
|
2020-11-16 17:16:39 +01:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-03 13:00:54 +01:00
|
|
|
/**
|
|
|
|
* CREATE TABLE
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async createTable (params) {
|
|
|
|
const {
|
|
|
|
name,
|
|
|
|
collation,
|
|
|
|
comment,
|
|
|
|
engine
|
|
|
|
} = params;
|
|
|
|
|
|
|
|
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
|
|
|
|
|
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-11-13 12:39:40 +01:00
|
|
|
/**
|
|
|
|
* ALTER TABLE
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async alterTable (params) {
|
|
|
|
const {
|
|
|
|
table,
|
2020-11-13 15:04:51 +01:00
|
|
|
additions,
|
|
|
|
deletions,
|
2020-11-16 17:16:39 +01:00
|
|
|
changes,
|
2020-12-01 16:48:20 +01:00
|
|
|
indexChanges,
|
2020-12-15 17:08:36 +01:00
|
|
|
foreignChanges,
|
2020-11-16 17:16:39 +01:00
|
|
|
options
|
2020-11-13 12:39:40 +01:00
|
|
|
} = params;
|
|
|
|
|
|
|
|
let sql = `ALTER TABLE \`${table}\` `;
|
|
|
|
const alterColumns = [];
|
|
|
|
|
2020-11-16 17:16:39 +01:00
|
|
|
// OPTIONS
|
|
|
|
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
|
|
|
|
if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`);
|
|
|
|
if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
|
|
|
|
if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`);
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// ADD FIELDS
|
2020-11-13 15:04:51 +01:00
|
|
|
additions.forEach(addition => {
|
|
|
|
const length = addition.numLength || addition.charLength || addition.datePrecision;
|
|
|
|
|
|
|
|
alterColumns.push(`ADD COLUMN \`${addition.name}\`
|
|
|
|
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
|
|
|
|
${addition.unsigned ? 'UNSIGNED' : ''}
|
2020-11-13 16:37:52 +01:00
|
|
|
${addition.zerofill ? 'ZEROFILL' : ''}
|
2020-11-13 15:04:51 +01:00
|
|
|
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
|
|
|
${addition.autoIncrement ? 'AUTO_INCREMENT' : ''}
|
|
|
|
${addition.default ? `DEFAULT ${addition.default}` : ''}
|
|
|
|
${addition.comment ? `COMMENT '${addition.comment}'` : ''}
|
|
|
|
${addition.collation ? `COLLATE ${addition.collation}` : ''}
|
|
|
|
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}
|
|
|
|
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
|
|
|
|
});
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// ADD INDEX
|
|
|
|
indexChanges.additions.forEach(addition => {
|
|
|
|
const fields = addition.fields.map(field => `\`${field}\``).join(',');
|
|
|
|
let type = addition.type;
|
|
|
|
|
|
|
|
if (type === 'PRIMARY')
|
|
|
|
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
|
|
|
else {
|
|
|
|
if (type === 'UNIQUE')
|
|
|
|
type = 'UNIQUE INDEX';
|
|
|
|
|
|
|
|
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-12-15 17:08:36 +01:00
|
|
|
// ADD FOREIGN KEYS
|
|
|
|
foreignChanges.additions.forEach(addition => {
|
|
|
|
alterColumns.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
|
|
|
|
});
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// CHANGE FIELDS
|
2020-11-13 12:39:40 +01:00
|
|
|
changes.forEach(change => {
|
|
|
|
const length = change.numLength || change.charLength || change.datePrecision;
|
|
|
|
|
|
|
|
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
|
|
|
|
${change.type.toUpperCase()}${length ? `(${length})` : ''}
|
|
|
|
${change.unsigned ? 'UNSIGNED' : ''}
|
2020-11-13 16:37:52 +01:00
|
|
|
${change.zerofill ? 'ZEROFILL' : ''}
|
2020-11-13 12:39:40 +01:00
|
|
|
${change.nullable ? 'NULL' : 'NOT NULL'}
|
|
|
|
${change.autoIncrement ? 'AUTO_INCREMENT' : ''}
|
|
|
|
${change.default ? `DEFAULT ${change.default}` : ''}
|
|
|
|
${change.comment ? `COMMENT '${change.comment}'` : ''}
|
|
|
|
${change.collation ? `COLLATE ${change.collation}` : ''}
|
|
|
|
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''}
|
|
|
|
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
|
|
|
|
});
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// CHANGE INDEX
|
|
|
|
indexChanges.changes.forEach(change => {
|
|
|
|
if (change.oldType === 'PRIMARY')
|
|
|
|
alterColumns.push('DROP PRIMARY KEY');
|
|
|
|
else
|
|
|
|
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
|
|
|
|
|
|
|
|
const fields = change.fields.map(field => `\`${field}\``).join(',');
|
|
|
|
let type = change.type;
|
|
|
|
|
|
|
|
if (type === 'PRIMARY')
|
|
|
|
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
|
|
|
else {
|
|
|
|
if (type === 'UNIQUE')
|
|
|
|
type = 'UNIQUE INDEX';
|
|
|
|
|
|
|
|
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-12-15 17:08:36 +01:00
|
|
|
// CHANGE FOREIGN KEYS
|
|
|
|
foreignChanges.changes.forEach(change => {
|
|
|
|
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
|
|
|
|
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
|
|
|
|
});
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// DROP FIELDS
|
2020-11-13 15:04:51 +01:00
|
|
|
deletions.forEach(deletion => {
|
|
|
|
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
|
|
|
|
});
|
|
|
|
|
2020-12-01 16:48:20 +01:00
|
|
|
// DROP INDEX
|
|
|
|
indexChanges.deletions.forEach(deletion => {
|
|
|
|
if (deletion.type === 'PRIMARY')
|
|
|
|
alterColumns.push('DROP PRIMARY KEY');
|
|
|
|
else
|
|
|
|
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
|
|
|
|
});
|
|
|
|
|
2020-12-15 17:08:36 +01:00
|
|
|
// DROP FOREIGN KEYS
|
|
|
|
foreignChanges.deletions.forEach(deletion => {
|
|
|
|
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
|
|
|
|
});
|
|
|
|
|
2020-11-13 12:39:40 +01:00
|
|
|
sql += alterColumns.join(', ');
|
|
|
|
|
2020-11-16 17:16:39 +01:00
|
|
|
// RENAME
|
|
|
|
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
|
|
|
|
|
2020-11-13 12:39:40 +01:00
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-12-03 16:15:10 +01:00
|
|
|
/**
|
|
|
|
* TRUNCATE TABLE
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async truncateTable (params) {
|
|
|
|
const sql = `TRUNCATE TABLE \`${params.table}\``;
|
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DROP TABLE
|
|
|
|
*
|
|
|
|
* @returns {Array.<Object>} parameters
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
async dropTable (params) {
|
2020-12-26 14:47:15 +01:00
|
|
|
const sql = `DROP TABLE \`${params.table}\``;// TODO: schema
|
2020-12-03 16:15:10 +01:00
|
|
|
return await this.raw(sql);
|
|
|
|
}
|
|
|
|
|
2020-09-17 17:58:12 +02:00
|
|
|
/**
|
2020-09-27 19:06:13 +02:00
|
|
|
* @returns {String} SQL string
|
2020-09-17 17:58:12 +02:00
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
|
|
|
getSQL () {
|
|
|
|
// SELECT
|
|
|
|
const selectArray = this._query.select.reduce(this._reducer, []);
|
|
|
|
let selectRaw = '';
|
|
|
|
|
|
|
|
if (selectArray.length)
|
|
|
|
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
|
|
|
|
|
|
|
// FROM
|
|
|
|
let fromRaw = '';
|
|
|
|
|
|
|
|
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
|
|
|
|
fromRaw = 'FROM';
|
|
|
|
else if (Object.keys(this._query.insert).length)
|
|
|
|
fromRaw = 'INTO';
|
|
|
|
|
|
|
|
fromRaw += this._query.from ? ` ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
|
|
|
|
|
|
|
// WHERE
|
|
|
|
const whereArray = this._query.where.reduce(this._reducer, []);
|
|
|
|
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
|
|
|
|
|
|
|
// UPDATE
|
|
|
|
const updateArray = this._query.update.reduce(this._reducer, []);
|
|
|
|
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
|
|
|
|
|
|
|
|
// INSERT
|
|
|
|
let insertRaw = '';
|
|
|
|
|
|
|
|
if (Object.keys(this._query.insert).length) {
|
|
|
|
const fieldsList = [];
|
|
|
|
const valueList = [];
|
|
|
|
const fields = this._query.insert;
|
|
|
|
|
|
|
|
for (const key in fields) {
|
|
|
|
if (fields[key] === null) continue;
|
|
|
|
fieldsList.push(key);
|
|
|
|
valueList.push(fields[key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
insertRaw = `(${fieldsList.join(', ')}) VALUES (${valueList.join(', ')}) `;
|
|
|
|
}
|
|
|
|
|
|
|
|
// GROUP BY
|
|
|
|
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
|
|
|
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
|
|
|
|
|
|
|
// ORDER BY
|
|
|
|
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
|
|
|
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
|
|
|
|
|
|
|
// LIMIT
|
|
|
|
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
|
|
|
|
|
|
|
|
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} sql raw SQL query
|
2020-10-27 16:41:00 +01:00
|
|
|
* @param {object} args
|
|
|
|
* @param {boolean} args.nest
|
|
|
|
* @param {boolean} args.details
|
2020-09-17 17:58:12 +02:00
|
|
|
* @returns {Promise}
|
|
|
|
* @memberof MySQLClient
|
|
|
|
*/
|
2020-10-27 16:41:00 +01:00
|
|
|
async raw (sql, args) {
|
|
|
|
args = {
|
|
|
|
nest: false,
|
|
|
|
details: false,
|
|
|
|
...args
|
|
|
|
};
|
|
|
|
const nestTables = args.nest ? '.' : false;
|
2020-09-17 17:58:12 +02:00
|
|
|
const resultsArr = [];
|
2020-10-27 16:41:00 +01:00
|
|
|
let paramsArr = [];
|
|
|
|
let selectedFields = [];
|
2020-09-17 17:58:12 +02:00
|
|
|
const queries = sql.split(';');
|
|
|
|
|
|
|
|
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
|
|
|
|
|
|
|
for (const query of queries) {
|
|
|
|
if (!query) continue;
|
2020-10-27 16:41:00 +01:00
|
|
|
let fieldsArr = [];
|
|
|
|
let keysArr = [];
|
|
|
|
|
|
|
|
const { rows, report, fields, keys } = await new Promise((resolve, reject) => {
|
|
|
|
this._connection.query({ sql: query, nestTables }, async (err, response, fields) => {
|
|
|
|
const queryResult = response;
|
2020-09-17 17:58:12 +02:00
|
|
|
|
|
|
|
if (err)
|
|
|
|
reject(err);
|
|
|
|
else {
|
2020-11-13 12:39:40 +01:00
|
|
|
const remappedFields = fields
|
|
|
|
? fields.map(field => {
|
|
|
|
return {
|
|
|
|
name: field.name,
|
|
|
|
orgName: field.orgName,
|
|
|
|
schema: field.db,
|
|
|
|
table: field.table,
|
|
|
|
orgTable: field.orgTable,
|
|
|
|
type: 'varchar'
|
|
|
|
};
|
|
|
|
})
|
|
|
|
: [];
|
2020-10-09 22:44:05 +02:00
|
|
|
|
2020-10-27 16:41:00 +01:00
|
|
|
if (args.details) {
|
|
|
|
let cachedTable;
|
|
|
|
|
|
|
|
if (remappedFields.length) {
|
|
|
|
selectedFields = remappedFields.map(field => {
|
|
|
|
return {
|
|
|
|
name: field.orgName || field.name,
|
|
|
|
table: field.orgTable || field.table
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
paramsArr = remappedFields.map(field => {
|
|
|
|
if (field.table) cachedTable = field.table;// Needed for some queries on information_schema
|
|
|
|
return {
|
|
|
|
table: field.orgTable || cachedTable,
|
|
|
|
schema: field.schema || 'INFORMATION_SCHEMA'
|
|
|
|
};
|
|
|
|
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
|
|
|
|
|
|
|
|
for (const paramObj of paramsArr) {
|
|
|
|
try { // Table data
|
|
|
|
const response = await this.getTableColumns(paramObj);
|
|
|
|
|
2020-11-13 12:39:40 +01:00
|
|
|
let detailedFields = response.length
|
|
|
|
? selectedFields.map(selField => {
|
|
|
|
return response.find(field => field.name === selField.name && field.table === selField.table);
|
|
|
|
}).filter(el => !!el)
|
|
|
|
: [];
|
2020-10-27 16:41:00 +01:00
|
|
|
|
|
|
|
if (selectedFields.length) {
|
|
|
|
detailedFields = detailedFields.map(field => {
|
|
|
|
const aliasObj = remappedFields.find(resField => resField.orgName === field.name);
|
|
|
|
return {
|
|
|
|
...field,
|
|
|
|
alias: aliasObj.name || field.name,
|
|
|
|
tableAlias: aliasObj.table || field.table
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!detailedFields.length) {
|
|
|
|
detailedFields = remappedFields.map(field => {
|
|
|
|
return {
|
|
|
|
...field,
|
|
|
|
alias: field.name,
|
|
|
|
tableAlias: field.table
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldsArr = fieldsArr ? [...fieldsArr, ...detailedFields] : detailedFields;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
try { // Key usage (foreign keys)
|
|
|
|
const response = await this.getKeyUsage(paramObj);
|
|
|
|
keysArr = keysArr ? [...keysArr, ...response] : response;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 17:58:12 +02:00
|
|
|
resolve({
|
2020-10-27 16:41:00 +01:00
|
|
|
rows: Array.isArray(queryResult) ? queryResult : false,
|
|
|
|
report: !Array.isArray(queryResult) ? queryResult : false,
|
|
|
|
fields: fieldsArr.length ? fieldsArr : remappedFields,
|
|
|
|
keys: keysArr
|
2020-09-17 17:58:12 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2020-10-27 16:41:00 +01:00
|
|
|
|
|
|
|
resultsArr.push({ rows, report, fields, keys });
|
2020-09-17 17:58:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
|
|
|
}
|
|
|
|
}
|