init source
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
'use strict';
|
||||
|
||||
const AbstractConnectionManager = require('../abstract/connection-manager');
|
||||
const SequelizeErrors = require('../../errors');
|
||||
const Promise = require('../../promise');
|
||||
const { logger } = require('../../utils/logger');
|
||||
const DataTypes = require('../../data-types').mysql;
|
||||
const momentTz = require('moment-timezone');
|
||||
const debug = logger.debugContext('connection:mysql');
|
||||
const parserStore = require('../parserStore')('mysql');
|
||||
|
||||
/**
|
||||
* MySQL Connection Manager
|
||||
*
|
||||
* Get connections, validate and disconnect them.
|
||||
* AbstractConnectionManager pooling use it to handle MySQL specific connections
|
||||
* Use https://github.com/sidorares/node-mysql2 to connect with MySQL server
|
||||
*
|
||||
* @extends AbstractConnectionManager
|
||||
* @returns Class<ConnectionManager>
|
||||
* @private
|
||||
*/
|
||||
|
||||
class ConnectionManager extends AbstractConnectionManager {
|
||||
constructor(dialect, sequelize) {
|
||||
sequelize.config.port = sequelize.config.port || 3306;
|
||||
super(dialect, sequelize);
|
||||
this.lib = this._loadDialectModule('mysql2');
|
||||
this.refreshTypeParser(DataTypes);
|
||||
}
|
||||
|
||||
_refreshTypeParser(dataType) {
|
||||
parserStore.refresh(dataType);
|
||||
}
|
||||
|
||||
_clearTypeParser() {
|
||||
parserStore.clear();
|
||||
}
|
||||
|
||||
static _typecast(field, next) {
|
||||
if (parserStore.get(field.type)) {
|
||||
return parserStore.get(field.type)(field, this.sequelize.options, next);
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with MySQL database based on config, Handle any errors in connection
|
||||
* Set the pool handlers on connection.error
|
||||
* Also set proper timezone once connection is connected.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @returns {Promise<Connection>}
|
||||
* @private
|
||||
*/
|
||||
connect(config) {
|
||||
const connectionConfig = Object.assign({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.username,
|
||||
flags: '-FOUND_ROWS',
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
timezone: this.sequelize.options.timezone,
|
||||
typeCast: ConnectionManager._typecast.bind(this),
|
||||
bigNumberStrings: false,
|
||||
supportBigNumbers: true
|
||||
}, config.dialectOptions);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = this.lib.createConnection(connectionConfig);
|
||||
|
||||
const errorHandler = e => {
|
||||
// clean up connect & error event if there is error
|
||||
connection.removeListener('connect', connectHandler);
|
||||
connection.removeListener('error', connectHandler);
|
||||
reject(e);
|
||||
};
|
||||
|
||||
const connectHandler = () => {
|
||||
// clean up error event if connected
|
||||
connection.removeListener('error', errorHandler);
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
// don't use connection.once for error event handling here
|
||||
// mysql2 emit error two times in case handshake was failed
|
||||
// first error is protocol_lost and second is timeout
|
||||
// if we will use `once.error` node process will crash on 2nd error emit
|
||||
connection.on('error', errorHandler);
|
||||
connection.once('connect', connectHandler);
|
||||
})
|
||||
.tap(() => { debug('connection acquired'); })
|
||||
.then(connection => {
|
||||
connection.on('error', error => {
|
||||
switch (error.code) {
|
||||
case 'ESOCKET':
|
||||
case 'ECONNRESET':
|
||||
case 'EPIPE':
|
||||
case 'PROTOCOL_CONNECTION_LOST':
|
||||
this.pool.destroy(connection);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.sequelize.config.keepDefaultTimezone) {
|
||||
// set timezone for this connection
|
||||
// but named timezone are not directly supported in mysql, so get its offset first
|
||||
let tzOffset = this.sequelize.options.timezone;
|
||||
tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset;
|
||||
return connection.query(`SET time_zone = '${tzOffset}'`, err => {
|
||||
if (err) { reject(err); } else { resolve(connection); }
|
||||
});
|
||||
}
|
||||
|
||||
// return connection without executing SET time_zone query
|
||||
resolve(connection);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
switch (err.code) {
|
||||
case 'ECONNREFUSED':
|
||||
throw new SequelizeErrors.ConnectionRefusedError(err);
|
||||
case 'ER_ACCESS_DENIED_ERROR':
|
||||
throw new SequelizeErrors.AccessDeniedError(err);
|
||||
case 'ENOTFOUND':
|
||||
throw new SequelizeErrors.HostNotFoundError(err);
|
||||
case 'EHOSTUNREACH':
|
||||
throw new SequelizeErrors.HostNotReachableError(err);
|
||||
case 'EINVAL':
|
||||
throw new SequelizeErrors.InvalidConnectionError(err);
|
||||
default:
|
||||
throw new SequelizeErrors.ConnectionError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
disconnect(connection) {
|
||||
// Don't disconnect connections with CLOSED state
|
||||
if (connection._closing) {
|
||||
debug('connection tried to disconnect but was already at CLOSED state');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.fromCallback(callback => connection.end(callback));
|
||||
}
|
||||
|
||||
validate(connection) {
|
||||
return connection
|
||||
&& !connection._fatalError
|
||||
&& !connection._protocolError
|
||||
&& !connection._closing
|
||||
&& !connection.stream.destroyed;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConnectionManager;
|
||||
module.exports.ConnectionManager = ConnectionManager;
|
||||
module.exports.default = ConnectionManager;
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
'use strict';
|
||||
|
||||
const wkx = require('wkx');
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment-timezone');
|
||||
module.exports = BaseTypes => {
|
||||
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html';
|
||||
|
||||
/**
|
||||
* types: [buffer_type, ...]
|
||||
* @see buffer_type here https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-codes.html
|
||||
* @see hex here https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js
|
||||
*/
|
||||
|
||||
BaseTypes.DATE.types.mysql = ['DATETIME'];
|
||||
BaseTypes.STRING.types.mysql = ['VAR_STRING'];
|
||||
BaseTypes.CHAR.types.mysql = ['STRING'];
|
||||
BaseTypes.TEXT.types.mysql = ['BLOB'];
|
||||
BaseTypes.TINYINT.types.mysql = ['TINY'];
|
||||
BaseTypes.SMALLINT.types.mysql = ['SHORT'];
|
||||
BaseTypes.MEDIUMINT.types.mysql = ['INT24'];
|
||||
BaseTypes.INTEGER.types.mysql = ['LONG'];
|
||||
BaseTypes.BIGINT.types.mysql = ['LONGLONG'];
|
||||
BaseTypes.FLOAT.types.mysql = ['FLOAT'];
|
||||
BaseTypes.TIME.types.mysql = ['TIME'];
|
||||
BaseTypes.DATEONLY.types.mysql = ['DATE'];
|
||||
BaseTypes.BOOLEAN.types.mysql = ['TINY'];
|
||||
BaseTypes.BLOB.types.mysql = ['TINYBLOB', 'BLOB', 'LONGBLOB'];
|
||||
BaseTypes.DECIMAL.types.mysql = ['NEWDECIMAL'];
|
||||
BaseTypes.UUID.types.mysql = false;
|
||||
BaseTypes.ENUM.types.mysql = false;
|
||||
BaseTypes.REAL.types.mysql = ['DOUBLE'];
|
||||
BaseTypes.DOUBLE.types.mysql = ['DOUBLE'];
|
||||
BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
|
||||
BaseTypes.JSON.types.mysql = ['JSON'];
|
||||
|
||||
class DECIMAL extends BaseTypes.DECIMAL {
|
||||
toSql() {
|
||||
let definition = super.toSql();
|
||||
if (this._unsigned) {
|
||||
definition += ' UNSIGNED';
|
||||
}
|
||||
if (this._zerofill) {
|
||||
definition += ' ZEROFILL';
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
|
||||
class DATE extends BaseTypes.DATE {
|
||||
toSql() {
|
||||
return `DATETIME${this._length ? `(${this._length})` : ''}`;
|
||||
}
|
||||
_stringify(date, options) {
|
||||
date = this._applyTimezone(date, options);
|
||||
// Fractional DATETIMEs only supported on MySQL 5.6.4+
|
||||
if (this._length) {
|
||||
return date.format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
}
|
||||
return date.format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
static parse(value, options) {
|
||||
value = value.string();
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
if (moment.tz.zone(options.timezone)) {
|
||||
value = moment.tz(value, options.timezone).toDate();
|
||||
}
|
||||
else {
|
||||
value = new Date(`${value} ${options.timezone}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class DATEONLY extends BaseTypes.DATEONLY {
|
||||
static parse(value) {
|
||||
return value.string();
|
||||
}
|
||||
}
|
||||
class UUID extends BaseTypes.UUID {
|
||||
toSql() {
|
||||
return 'CHAR(36) BINARY';
|
||||
}
|
||||
}
|
||||
|
||||
const SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON'];
|
||||
|
||||
class GEOMETRY extends BaseTypes.GEOMETRY {
|
||||
constructor(type, srid) {
|
||||
super(type, srid);
|
||||
if (_.isEmpty(this.type)) {
|
||||
this.sqlType = this.key;
|
||||
return;
|
||||
}
|
||||
if (SUPPORTED_GEOMETRY_TYPES.includes(this.type)) {
|
||||
this.sqlType = this.type;
|
||||
return;
|
||||
}
|
||||
throw new Error(`Supported geometry types are: ${SUPPORTED_GEOMETRY_TYPES.join(', ')}`);
|
||||
}
|
||||
static parse(value) {
|
||||
value = value.buffer();
|
||||
// Empty buffer, MySQL doesn't support POINT EMPTY
|
||||
// check, https://dev.mysql.com/worklog/task/?id=2381
|
||||
if (!value || value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// For some reason, discard the first 4 bytes
|
||||
value = value.slice(4);
|
||||
return wkx.Geometry.parse(value).toGeoJSON();
|
||||
}
|
||||
toSql() {
|
||||
return this.sqlType;
|
||||
}
|
||||
}
|
||||
|
||||
class ENUM extends BaseTypes.ENUM {
|
||||
toSql(options) {
|
||||
return `ENUM(${this.values.map(value => options.escape(value)).join(', ')})`;
|
||||
}
|
||||
}
|
||||
|
||||
class JSONTYPE extends BaseTypes.JSON {
|
||||
_stringify(value, options) {
|
||||
return options.operation === 'where' && typeof value === 'string' ? value : JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ENUM,
|
||||
DATE,
|
||||
DATEONLY,
|
||||
UUID,
|
||||
GEOMETRY,
|
||||
DECIMAL,
|
||||
JSON: JSONTYPE
|
||||
};
|
||||
};
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const AbstractDialect = require('../abstract');
|
||||
const ConnectionManager = require('./connection-manager');
|
||||
const Query = require('./query');
|
||||
const QueryGenerator = require('./query-generator');
|
||||
const DataTypes = require('../../data-types').mysql;
|
||||
|
||||
class MysqlDialect extends AbstractDialect {
|
||||
constructor(sequelize) {
|
||||
super();
|
||||
this.sequelize = sequelize;
|
||||
this.connectionManager = new ConnectionManager(this, sequelize);
|
||||
this.QueryGenerator = new QueryGenerator({
|
||||
_dialect: this,
|
||||
sequelize
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), {
|
||||
'VALUES ()': true,
|
||||
'LIMIT ON UPDATE': true,
|
||||
lock: true,
|
||||
forShare: 'LOCK IN SHARE MODE',
|
||||
settingIsolationLevelDuringTransaction: false,
|
||||
inserts: {
|
||||
ignoreDuplicates: ' IGNORE',
|
||||
updateOnDuplicate: ' ON DUPLICATE KEY UPDATE'
|
||||
},
|
||||
index: {
|
||||
collate: false,
|
||||
length: true,
|
||||
parser: true,
|
||||
type: true,
|
||||
using: 1
|
||||
},
|
||||
constraints: {
|
||||
dropConstraint: false,
|
||||
check: false
|
||||
},
|
||||
indexViaAlter: true,
|
||||
indexHints: true,
|
||||
NUMERIC: true,
|
||||
GEOMETRY: true,
|
||||
JSON: true,
|
||||
REGEXP: true
|
||||
});
|
||||
|
||||
ConnectionManager.prototype.defaultVersion = '5.6.0';
|
||||
MysqlDialect.prototype.Query = Query;
|
||||
MysqlDialect.prototype.QueryGenerator = QueryGenerator;
|
||||
MysqlDialect.prototype.DataTypes = DataTypes;
|
||||
MysqlDialect.prototype.name = 'mysql';
|
||||
MysqlDialect.prototype.TICK_CHAR = '`';
|
||||
MysqlDialect.prototype.TICK_CHAR_LEFT = MysqlDialect.prototype.TICK_CHAR;
|
||||
MysqlDialect.prototype.TICK_CHAR_RIGHT = MysqlDialect.prototype.TICK_CHAR;
|
||||
|
||||
module.exports = MysqlDialect;
|
||||
+541
@@ -0,0 +1,541 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const Utils = require('../../utils');
|
||||
const AbstractQueryGenerator = require('../abstract/query-generator');
|
||||
const util = require('util');
|
||||
const Op = require('../../operators');
|
||||
|
||||
|
||||
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
|
||||
const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
|
||||
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
|
||||
const foreignKeyFields = 'CONSTRAINT_NAME as constraint_name,'
|
||||
+ 'CONSTRAINT_NAME as constraintName,'
|
||||
+ 'CONSTRAINT_SCHEMA as constraintSchema,'
|
||||
+ 'CONSTRAINT_SCHEMA as constraintCatalog,'
|
||||
+ 'TABLE_NAME as tableName,'
|
||||
+ 'TABLE_SCHEMA as tableSchema,'
|
||||
+ 'TABLE_SCHEMA as tableCatalog,'
|
||||
+ 'COLUMN_NAME as columnName,'
|
||||
+ 'REFERENCED_TABLE_SCHEMA as referencedTableSchema,'
|
||||
+ 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog,'
|
||||
+ 'REFERENCED_TABLE_NAME as referencedTableName,'
|
||||
+ 'REFERENCED_COLUMN_NAME as referencedColumnName';
|
||||
|
||||
const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']);
|
||||
|
||||
class MySQLQueryGenerator extends AbstractQueryGenerator {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.OperatorMap = Object.assign({}, this.OperatorMap, {
|
||||
[Op.regexp]: 'REGEXP',
|
||||
[Op.notRegexp]: 'NOT REGEXP'
|
||||
});
|
||||
}
|
||||
|
||||
createDatabaseQuery(databaseName, options) {
|
||||
options = Object.assign({
|
||||
charset: null,
|
||||
collate: null
|
||||
}, options || {});
|
||||
|
||||
const database = this.quoteIdentifier(databaseName);
|
||||
const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : '';
|
||||
const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : '';
|
||||
|
||||
return `${`CREATE DATABASE IF NOT EXISTS ${database}${charset}${collate}`.trim()};`;
|
||||
}
|
||||
|
||||
dropDatabaseQuery(databaseName) {
|
||||
return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName).trim()};`;
|
||||
}
|
||||
|
||||
createSchema() {
|
||||
return 'SHOW TABLES';
|
||||
}
|
||||
|
||||
showSchemasQuery() {
|
||||
return 'SHOW TABLES';
|
||||
}
|
||||
|
||||
versionQuery() {
|
||||
return 'SELECT VERSION() as `version`';
|
||||
}
|
||||
|
||||
createTableQuery(tableName, attributes, options) {
|
||||
options = Object.assign({
|
||||
engine: 'InnoDB',
|
||||
charset: null,
|
||||
rowFormat: null
|
||||
}, options || {});
|
||||
|
||||
const primaryKeys = [];
|
||||
const foreignKeys = {};
|
||||
const attrStr = [];
|
||||
|
||||
for (const attr in attributes) {
|
||||
if (!Object.prototype.hasOwnProperty.call(attributes, attr)) continue;
|
||||
const dataType = attributes[attr];
|
||||
let match;
|
||||
|
||||
if (dataType.includes('PRIMARY KEY')) {
|
||||
primaryKeys.push(attr);
|
||||
|
||||
if (dataType.includes('REFERENCES')) {
|
||||
// MySQL doesn't support inline REFERENCES declarations: move to the end
|
||||
match = dataType.match(/^(.+) (REFERENCES.*)$/);
|
||||
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
|
||||
foreignKeys[attr] = match[2];
|
||||
} else {
|
||||
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
|
||||
}
|
||||
} else if (dataType.includes('REFERENCES')) {
|
||||
// MySQL doesn't support inline REFERENCES declarations: move to the end
|
||||
match = dataType.match(/^(.+) (REFERENCES.*)$/);
|
||||
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
|
||||
foreignKeys[attr] = match[2];
|
||||
} else {
|
||||
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`);
|
||||
}
|
||||
}
|
||||
|
||||
const table = this.quoteTable(tableName);
|
||||
let attributesClause = attrStr.join(', ');
|
||||
const comment = options.comment && typeof options.comment === 'string' ? ` COMMENT ${this.escape(options.comment)}` : '';
|
||||
const engine = options.engine;
|
||||
const charset = options.charset ? ` DEFAULT CHARSET=${options.charset}` : '';
|
||||
const collation = options.collate ? ` COLLATE ${options.collate}` : '';
|
||||
const rowFormat = options.rowFormat ? ` ROW_FORMAT=${options.rowFormat}` : '';
|
||||
const initialAutoIncrement = options.initialAutoIncrement ? ` AUTO_INCREMENT=${options.initialAutoIncrement}` : '';
|
||||
const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');
|
||||
|
||||
if (options.uniqueKeys) {
|
||||
_.each(options.uniqueKeys, (columns, indexName) => {
|
||||
if (columns.customIndex) {
|
||||
if (typeof indexName !== 'string') {
|
||||
indexName = `uniq_${tableName}_${columns.fields.join('_')}`;
|
||||
}
|
||||
attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (pkString.length > 0) {
|
||||
attributesClause += `, PRIMARY KEY (${pkString})`;
|
||||
}
|
||||
|
||||
for (const fkey in foreignKeys) {
|
||||
if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) {
|
||||
attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `CREATE TABLE IF NOT EXISTS ${table} (${attributesClause}) ENGINE=${engine}${comment}${charset}${collation}${initialAutoIncrement}${rowFormat};`;
|
||||
}
|
||||
|
||||
|
||||
describeTableQuery(tableName, schema, schemaDelimiter) {
|
||||
const table = this.quoteTable(
|
||||
this.addSchema({
|
||||
tableName,
|
||||
_schema: schema,
|
||||
_schemaDelimiter: schemaDelimiter
|
||||
})
|
||||
);
|
||||
|
||||
return `SHOW FULL COLUMNS FROM ${table};`;
|
||||
}
|
||||
|
||||
showTablesQuery(database) {
|
||||
let query = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\'';
|
||||
if (database) {
|
||||
query += ` AND TABLE_SCHEMA = ${this.escape(database)}`;
|
||||
} else {
|
||||
query += ' AND TABLE_SCHEMA NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'SYS\')';
|
||||
}
|
||||
return `${query};`;
|
||||
}
|
||||
|
||||
addColumnQuery(table, key, dataType) {
|
||||
const definition = this.attributeToSQL(dataType, {
|
||||
context: 'addColumn',
|
||||
tableName: table,
|
||||
foreignKey: key
|
||||
});
|
||||
|
||||
return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition};`;
|
||||
}
|
||||
|
||||
removeColumnQuery(tableName, attributeName) {
|
||||
return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(attributeName)};`;
|
||||
}
|
||||
|
||||
changeColumnQuery(tableName, attributes) {
|
||||
const attrString = [];
|
||||
const constraintString = [];
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
let definition = attributes[attributeName];
|
||||
if (definition.includes('REFERENCES')) {
|
||||
const attrName = this.quoteIdentifier(attributeName);
|
||||
definition = definition.replace(/.+?(?=REFERENCES)/, '');
|
||||
constraintString.push(`FOREIGN KEY (${attrName}) ${definition}`);
|
||||
} else {
|
||||
attrString.push(`\`${attributeName}\` \`${attributeName}\` ${definition}`);
|
||||
}
|
||||
}
|
||||
|
||||
let finalQuery = '';
|
||||
if (attrString.length) {
|
||||
finalQuery += `CHANGE ${attrString.join(', ')}`;
|
||||
finalQuery += constraintString.length ? ' ' : '';
|
||||
}
|
||||
if (constraintString.length) {
|
||||
finalQuery += `ADD ${constraintString.join(', ')}`;
|
||||
}
|
||||
|
||||
return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`;
|
||||
}
|
||||
|
||||
renameColumnQuery(tableName, attrBefore, attributes) {
|
||||
const attrString = [];
|
||||
|
||||
for (const attrName in attributes) {
|
||||
const definition = attributes[attrName];
|
||||
attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`);
|
||||
}
|
||||
|
||||
return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`;
|
||||
}
|
||||
|
||||
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
|
||||
if (smth instanceof Utils.Json) {
|
||||
// Parse nested object
|
||||
if (smth.conditions) {
|
||||
const conditions = this.parseConditionObject(smth.conditions).map(condition =>
|
||||
`${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'`
|
||||
);
|
||||
|
||||
return conditions.join(' AND ');
|
||||
}
|
||||
if (smth.path) {
|
||||
let str;
|
||||
|
||||
// Allow specifying conditions using the sqlite json functions
|
||||
if (this._checkValidJsonStatement(smth.path)) {
|
||||
str = smth.path;
|
||||
} else {
|
||||
// Also support json property accessors
|
||||
const paths = _.toPath(smth.path);
|
||||
const column = paths.shift();
|
||||
str = this.jsonPathExtractionQuery(column, paths);
|
||||
}
|
||||
|
||||
if (smth.value) {
|
||||
str += util.format(' = %s', this.escape(smth.value));
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
} else if (smth instanceof Utils.Cast) {
|
||||
if (/timestamp/i.test(smth.type)) {
|
||||
smth.type = 'datetime';
|
||||
} else if (smth.json && /boolean/i.test(smth.type)) {
|
||||
// true or false cannot be casted as booleans within a JSON structure
|
||||
smth.type = 'char';
|
||||
} else if (/double precision/i.test(smth.type) || /boolean/i.test(smth.type) || /integer/i.test(smth.type)) {
|
||||
smth.type = 'decimal';
|
||||
} else if (/text/i.test(smth.type)) {
|
||||
smth.type = 'char';
|
||||
}
|
||||
}
|
||||
|
||||
return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
|
||||
}
|
||||
|
||||
_toJSONValue(value) {
|
||||
// true/false are stored as strings in mysql
|
||||
if (typeof value === 'boolean') {
|
||||
return value.toString();
|
||||
}
|
||||
// null is stored as a string in mysql
|
||||
if (value === null) {
|
||||
return 'null';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
|
||||
options.onDuplicate = 'UPDATE ';
|
||||
|
||||
options.onDuplicate += Object.keys(updateValues).map(key => {
|
||||
key = this.quoteIdentifier(key);
|
||||
return `${key}=VALUES(${key})`;
|
||||
}).join(', ');
|
||||
|
||||
return this.insertQuery(tableName, insertValues, model.rawAttributes, options);
|
||||
}
|
||||
|
||||
truncateTableQuery(tableName) {
|
||||
return `TRUNCATE ${this.quoteTable(tableName)}`;
|
||||
}
|
||||
|
||||
deleteQuery(tableName, where, options = {}, model) {
|
||||
let limit = '';
|
||||
let query = `DELETE FROM ${this.quoteTable(tableName)}`;
|
||||
|
||||
if (options.limit) {
|
||||
limit = ` LIMIT ${this.escape(options.limit)}`;
|
||||
}
|
||||
|
||||
where = this.getWhereConditions(where, null, model, options);
|
||||
|
||||
if (where) {
|
||||
query += ` WHERE ${where}`;
|
||||
}
|
||||
|
||||
return query + limit;
|
||||
}
|
||||
|
||||
showIndexesQuery(tableName, options) {
|
||||
return `SHOW INDEX FROM ${this.quoteTable(tableName)}${(options || {}).database ? ` FROM \`${options.database}\`` : ''}`;
|
||||
}
|
||||
|
||||
showConstraintsQuery(table, constraintName) {
|
||||
const tableName = table.tableName || table;
|
||||
const schemaName = table.schema;
|
||||
|
||||
let sql = [
|
||||
'SELECT CONSTRAINT_CATALOG AS constraintCatalog,',
|
||||
'CONSTRAINT_NAME AS constraintName,',
|
||||
'CONSTRAINT_SCHEMA AS constraintSchema,',
|
||||
'CONSTRAINT_TYPE AS constraintType,',
|
||||
'TABLE_NAME AS tableName,',
|
||||
'TABLE_SCHEMA AS tableSchema',
|
||||
'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS',
|
||||
`WHERE table_name='${tableName}'`
|
||||
].join(' ');
|
||||
|
||||
if (constraintName) {
|
||||
sql += ` AND constraint_name = '${constraintName}'`;
|
||||
}
|
||||
|
||||
if (schemaName) {
|
||||
sql += ` AND TABLE_SCHEMA = '${schemaName}'`;
|
||||
}
|
||||
|
||||
return `${sql};`;
|
||||
}
|
||||
|
||||
removeIndexQuery(tableName, indexNameOrAttributes) {
|
||||
let indexName = indexNameOrAttributes;
|
||||
|
||||
if (typeof indexName !== 'string') {
|
||||
indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
|
||||
}
|
||||
|
||||
return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`;
|
||||
}
|
||||
|
||||
attributeToSQL(attribute, options) {
|
||||
if (!_.isPlainObject(attribute)) {
|
||||
attribute = {
|
||||
type: attribute
|
||||
};
|
||||
}
|
||||
|
||||
const attributeString = attribute.type.toString({ escape: this.escape.bind(this) });
|
||||
let template = attributeString;
|
||||
|
||||
if (attribute.allowNull === false) {
|
||||
template += ' NOT NULL';
|
||||
}
|
||||
|
||||
if (attribute.autoIncrement) {
|
||||
template += ' auto_increment';
|
||||
}
|
||||
|
||||
// BLOB/TEXT/GEOMETRY/JSON cannot have a default value
|
||||
if (!typeWithoutDefault.has(attributeString)
|
||||
&& attribute.type._binary !== true
|
||||
&& Utils.defaultValueSchemable(attribute.defaultValue)) {
|
||||
template += ` DEFAULT ${this.escape(attribute.defaultValue)}`;
|
||||
}
|
||||
|
||||
if (attribute.unique === true) {
|
||||
template += ' UNIQUE';
|
||||
}
|
||||
|
||||
if (attribute.primaryKey) {
|
||||
template += ' PRIMARY KEY';
|
||||
}
|
||||
|
||||
if (attribute.comment) {
|
||||
template += ` COMMENT ${this.escape(attribute.comment)}`;
|
||||
}
|
||||
|
||||
if (attribute.first) {
|
||||
template += ' FIRST';
|
||||
}
|
||||
if (attribute.after) {
|
||||
template += ` AFTER ${this.quoteIdentifier(attribute.after)}`;
|
||||
}
|
||||
|
||||
if (attribute.references) {
|
||||
|
||||
if (options && options.context === 'addColumn' && options.foreignKey) {
|
||||
const attrName = this.quoteIdentifier(options.foreignKey);
|
||||
const fkName = this.quoteIdentifier(`${options.tableName}_${attrName}_foreign_idx`);
|
||||
|
||||
template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`;
|
||||
}
|
||||
|
||||
template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`;
|
||||
|
||||
if (attribute.references.key) {
|
||||
template += ` (${this.quoteIdentifier(attribute.references.key)})`;
|
||||
} else {
|
||||
template += ` (${this.quoteIdentifier('id')})`;
|
||||
}
|
||||
|
||||
if (attribute.onDelete) {
|
||||
template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`;
|
||||
}
|
||||
|
||||
if (attribute.onUpdate) {
|
||||
template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`;
|
||||
}
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
attributesToSQL(attributes, options) {
|
||||
const result = {};
|
||||
|
||||
for (const key in attributes) {
|
||||
const attribute = attributes[key];
|
||||
result[attribute.field || key] = this.attributeToSQL(attribute, options);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the statmement is json function or simple path
|
||||
*
|
||||
* @param {string} stmt The statement to validate
|
||||
* @returns {boolean} true if the given statement is json function
|
||||
* @throws {Error} throw if the statement looks like json function but has invalid token
|
||||
* @private
|
||||
*/
|
||||
_checkValidJsonStatement(stmt) {
|
||||
if (typeof stmt !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
let openingBrackets = 0;
|
||||
let closingBrackets = 0;
|
||||
let hasJsonFunction = false;
|
||||
let hasInvalidToken = false;
|
||||
|
||||
while (currentIndex < stmt.length) {
|
||||
const string = stmt.substr(currentIndex);
|
||||
const functionMatches = jsonFunctionRegex.exec(string);
|
||||
if (functionMatches) {
|
||||
currentIndex += functionMatches[0].indexOf('(');
|
||||
hasJsonFunction = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const operatorMatches = jsonOperatorRegex.exec(string);
|
||||
if (operatorMatches) {
|
||||
currentIndex += operatorMatches[0].length;
|
||||
hasJsonFunction = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const tokenMatches = tokenCaptureRegex.exec(string);
|
||||
if (tokenMatches) {
|
||||
const capturedToken = tokenMatches[1];
|
||||
if (capturedToken === '(') {
|
||||
openingBrackets++;
|
||||
} else if (capturedToken === ')') {
|
||||
closingBrackets++;
|
||||
} else if (capturedToken === ';') {
|
||||
hasInvalidToken = true;
|
||||
break;
|
||||
}
|
||||
currentIndex += tokenMatches[0].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Check invalid json statement
|
||||
if (hasJsonFunction && (hasInvalidToken || openingBrackets !== closingBrackets)) {
|
||||
throw new Error(`Invalid json statement: ${stmt}`);
|
||||
}
|
||||
|
||||
// return true if the statement has valid json function
|
||||
return hasJsonFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SQL query that returns all foreign keys of a table.
|
||||
*
|
||||
* @param {Object} table The table.
|
||||
* @param {string} schemaName The name of the schema.
|
||||
* @returns {string} The generated sql query.
|
||||
* @private
|
||||
*/
|
||||
getForeignKeysQuery(table, schemaName) {
|
||||
const tableName = table.tableName || table;
|
||||
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SQL query that returns the foreign key constraint of a given column.
|
||||
*
|
||||
* @param {Object} table The table.
|
||||
* @param {string} columnName The name of the column.
|
||||
* @returns {string} The generated sql query.
|
||||
* @private
|
||||
*/
|
||||
getForeignKeyQuery(table, columnName) {
|
||||
const quotedSchemaName = table.schema ? wrapSingleQuote(table.schema) : '';
|
||||
const quotedTableName = wrapSingleQuote(table.tableName || table);
|
||||
const quotedColumnName = wrapSingleQuote(columnName);
|
||||
|
||||
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE`
|
||||
+ ` WHERE (REFERENCED_TABLE_NAME = ${quotedTableName}${table.schema
|
||||
? ` AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`
|
||||
: ''} AND REFERENCED_COLUMN_NAME = ${quotedColumnName})`
|
||||
+ ` OR (TABLE_NAME = ${quotedTableName}${table.schema ?
|
||||
` AND TABLE_SCHEMA = ${quotedSchemaName}` : ''} AND COLUMN_NAME = ${quotedColumnName} AND REFERENCED_TABLE_NAME IS NOT NULL)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SQL query that removes a foreign key from a table.
|
||||
*
|
||||
* @param {string} tableName The name of the table.
|
||||
* @param {string} foreignKey The name of the foreign key constraint.
|
||||
* @returns {string} The generated sql query.
|
||||
* @private
|
||||
*/
|
||||
dropForeignKeyQuery(tableName, foreignKey) {
|
||||
return `ALTER TABLE ${this.quoteTable(tableName)}
|
||||
DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`;
|
||||
}
|
||||
}
|
||||
|
||||
// private methods
|
||||
function wrapSingleQuote(identifier) {
|
||||
return Utils.addTicks(identifier, '\'');
|
||||
}
|
||||
|
||||
module.exports = MySQLQueryGenerator;
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
Returns an object that treats MySQL's inabilities to do certain queries.
|
||||
|
||||
@class QueryInterface
|
||||
@static
|
||||
@private
|
||||
*/
|
||||
|
||||
const Promise = require('../../promise');
|
||||
const sequelizeErrors = require('../../errors');
|
||||
|
||||
/**
|
||||
A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint.
|
||||
|
||||
@param {QueryInterface} qi
|
||||
@param {string} tableName The name of the table.
|
||||
@param {string} columnName The name of the attribute that we want to remove.
|
||||
@param {Object} options
|
||||
|
||||
@private
|
||||
*/
|
||||
function removeColumn(qi, tableName, columnName, options) {
|
||||
options = options || {};
|
||||
|
||||
return qi.sequelize.query(
|
||||
qi.QueryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : {
|
||||
tableName,
|
||||
schema: qi.sequelize.config.database
|
||||
}, columnName),
|
||||
Object.assign({ raw: true }, options)
|
||||
)
|
||||
.then(([results]) => {
|
||||
//Exclude primary key constraint
|
||||
if (!results.length || results[0].constraint_name === 'PRIMARY') {
|
||||
// No foreign key constraints found, so we can remove the column
|
||||
return;
|
||||
}
|
||||
return Promise.map(results, constraint => qi.sequelize.query(
|
||||
qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name),
|
||||
Object.assign({ raw: true }, options)
|
||||
));
|
||||
})
|
||||
.then(() => qi.sequelize.query(
|
||||
qi.QueryGenerator.removeColumnQuery(tableName, columnName),
|
||||
Object.assign({ raw: true }, options)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {QueryInterface} qi
|
||||
* @param {string} tableName
|
||||
* @param {string} constraintName
|
||||
* @param {Object} options
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function removeConstraint(qi, tableName, constraintName, options) {
|
||||
const sql = qi.QueryGenerator.showConstraintsQuery(
|
||||
tableName.tableName ? tableName : {
|
||||
tableName,
|
||||
schema: qi.sequelize.config.database
|
||||
}, constraintName);
|
||||
|
||||
return qi.sequelize.query(sql, Object.assign({}, options,
|
||||
{ type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS }))
|
||||
.then(constraints => {
|
||||
const constraint = constraints[0];
|
||||
let query;
|
||||
if (!constraint || !constraint.constraintType) {
|
||||
throw new sequelizeErrors.UnknownConstraintError(
|
||||
{
|
||||
message: `Constraint ${constraintName} on table ${tableName} does not exist`,
|
||||
constraint: constraintName,
|
||||
table: tableName
|
||||
});
|
||||
}
|
||||
|
||||
if (constraint.constraintType === 'FOREIGN KEY') {
|
||||
query = qi.QueryGenerator.dropForeignKeyQuery(tableName, constraintName);
|
||||
} else {
|
||||
query = qi.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName);
|
||||
}
|
||||
|
||||
return qi.sequelize.query(query, options);
|
||||
});
|
||||
}
|
||||
|
||||
exports.removeConstraint = removeConstraint;
|
||||
exports.removeColumn = removeColumn;
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
'use strict';
|
||||
|
||||
const Utils = require('../../utils');
|
||||
const AbstractQuery = require('../abstract/query');
|
||||
const sequelizeErrors = require('../../errors');
|
||||
const _ = require('lodash');
|
||||
const { logger } = require('../../utils/logger');
|
||||
|
||||
const debug = logger.debugContext('sql:mysql');
|
||||
|
||||
|
||||
class Query extends AbstractQuery {
|
||||
constructor(connection, sequelize, options) {
|
||||
super(connection, sequelize, Object.assign({ showWarnings: false }, options));
|
||||
}
|
||||
|
||||
static formatBindParameters(sql, values, dialect) {
|
||||
const bindParam = [];
|
||||
const replacementFunc = (match, key, values) => {
|
||||
if (values[key] !== undefined) {
|
||||
bindParam.push(values[key]);
|
||||
return '?';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
|
||||
return [sql, bindParam.length > 0 ? bindParam : undefined];
|
||||
}
|
||||
|
||||
run(sql, parameters) {
|
||||
this.sql = sql;
|
||||
const { connection, options } = this;
|
||||
|
||||
//do we need benchmark for this query execution
|
||||
const showWarnings = this.sequelize.options.showWarnings || options.showWarnings;
|
||||
|
||||
const complete = this._logQuery(sql, debug, parameters);
|
||||
|
||||
return new Utils.Promise((resolve, reject) => {
|
||||
const handler = (err, results) => {
|
||||
complete();
|
||||
|
||||
if (err) {
|
||||
// MySQL automatically rolls-back transactions in the event of a deadlock
|
||||
if (options.transaction && err.errno === 1213) {
|
||||
options.transaction.finished = 'rollback';
|
||||
}
|
||||
err.sql = sql;
|
||||
err.parameters = parameters;
|
||||
|
||||
reject(this.formatError(err));
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
};
|
||||
if (parameters) {
|
||||
debug('parameters(%j)', parameters);
|
||||
connection.execute(sql, parameters, handler).setMaxListeners(100);
|
||||
} else {
|
||||
connection.query({ sql }, handler).setMaxListeners(100);
|
||||
}
|
||||
})
|
||||
// Log warnings if we've got them.
|
||||
.then(results => {
|
||||
if (showWarnings && results && results.warningStatus > 0) {
|
||||
return this.logWarnings(results);
|
||||
}
|
||||
return results;
|
||||
})
|
||||
// Return formatted results...
|
||||
.then(results => this.formatResults(results));
|
||||
}
|
||||
|
||||
/**
|
||||
* High level function that handles the results of a query execution.
|
||||
*
|
||||
*
|
||||
* Example:
|
||||
* query.formatResults([
|
||||
* {
|
||||
* id: 1, // this is from the main table
|
||||
* attr2: 'snafu', // this is from the main table
|
||||
* Tasks.id: 1, // this is from the associated table
|
||||
* Tasks.title: 'task' // this is from the associated table
|
||||
* }
|
||||
* ])
|
||||
*
|
||||
* @param {Array} data - The result of the query execution.
|
||||
* @private
|
||||
*/
|
||||
formatResults(data) {
|
||||
let result = this.instance;
|
||||
|
||||
if (this.isInsertQuery(data)) {
|
||||
this.handleInsertQuery(data);
|
||||
|
||||
if (!this.instance) {
|
||||
// handle bulkCreate AI primiary key
|
||||
if (
|
||||
data.constructor.name === 'ResultSetHeader'
|
||||
&& this.model
|
||||
&& this.model.autoIncrementAttribute
|
||||
&& this.model.autoIncrementAttribute === this.model.primaryKeyAttribute
|
||||
&& this.model.rawAttributes[this.model.primaryKeyAttribute]
|
||||
) {
|
||||
const startId = data[this.getInsertIdField()];
|
||||
result = [];
|
||||
for (let i = startId; i < startId + data.affectedRows; i++) {
|
||||
result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i });
|
||||
}
|
||||
} else {
|
||||
result = data[this.getInsertIdField()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isSelectQuery()) {
|
||||
return this.handleSelectQuery(data);
|
||||
}
|
||||
if (this.isShowTablesQuery()) {
|
||||
return this.handleShowTablesQuery(data);
|
||||
}
|
||||
if (this.isDescribeQuery()) {
|
||||
result = {};
|
||||
|
||||
for (const _result of data) {
|
||||
const enumRegex = /^enum/i;
|
||||
result[_result.Field] = {
|
||||
type: enumRegex.test(_result.Type) ? _result.Type.replace(enumRegex, 'ENUM') : _result.Type.toUpperCase(),
|
||||
allowNull: _result.Null === 'YES',
|
||||
defaultValue: _result.Default,
|
||||
primaryKey: _result.Key === 'PRI',
|
||||
autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment',
|
||||
comment: _result.Comment ? _result.Comment : null
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (this.isShowIndexesQuery()) {
|
||||
return this.handleShowIndexesQuery(data);
|
||||
}
|
||||
if (this.isCallQuery()) {
|
||||
return data[0];
|
||||
}
|
||||
if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) {
|
||||
return data.affectedRows;
|
||||
}
|
||||
if (this.isVersionQuery()) {
|
||||
return data[0].version;
|
||||
}
|
||||
if (this.isForeignKeysQuery()) {
|
||||
return data;
|
||||
}
|
||||
if (this.isInsertQuery() || this.isUpdateQuery()) {
|
||||
return [result, data.affectedRows];
|
||||
}
|
||||
if (this.isShowConstraintsQuery()) {
|
||||
return data;
|
||||
}
|
||||
if (this.isRawQuery()) {
|
||||
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
|
||||
return [data, data];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
logWarnings(results) {
|
||||
return this.run('SHOW WARNINGS').then(warningResults => {
|
||||
const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `;
|
||||
const messages = [];
|
||||
for (const _warningRow of warningResults) {
|
||||
if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') continue;
|
||||
for (const _warningResult of _warningRow) {
|
||||
if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) {
|
||||
messages.push(_warningResult.Message);
|
||||
} else {
|
||||
for (const _objectKey of _warningResult.keys()) {
|
||||
messages.push([_objectKey, _warningResult[_objectKey]].join(': '));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.sequelize.log(warningMessage + messages.join('; '), this.options);
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
formatError(err) {
|
||||
const errCode = err.errno || err.code;
|
||||
|
||||
switch (errCode) {
|
||||
case 1062: {
|
||||
const match = err.message.match(/Duplicate entry '([\s\S]*)' for key '?((.|\s)*?)'?$/);
|
||||
let fields = {};
|
||||
let message = 'Validation error';
|
||||
const values = match ? match[1].split('-') : undefined;
|
||||
const fieldKey = match ? match[2] : undefined;
|
||||
const fieldVal = match ? match[1] : undefined;
|
||||
const uniqueKey = this.model && this.model.uniqueKeys[fieldKey];
|
||||
|
||||
if (uniqueKey) {
|
||||
if (uniqueKey.msg) message = uniqueKey.msg;
|
||||
fields = _.zipObject(uniqueKey.fields, values);
|
||||
} else {
|
||||
fields[fieldKey] = fieldVal;
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
_.forOwn(fields, (value, field) => {
|
||||
errors.push(new sequelizeErrors.ValidationErrorItem(
|
||||
this.getUniqueConstraintErrorMessage(field),
|
||||
'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB,
|
||||
field,
|
||||
value,
|
||||
this.instance,
|
||||
'not_unique'
|
||||
));
|
||||
});
|
||||
|
||||
return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields });
|
||||
}
|
||||
|
||||
case 1451:
|
||||
case 1452: {
|
||||
// e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`)
|
||||
const match = err.message.match(/CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/);
|
||||
const quoteChar = match ? match[1] : '`';
|
||||
const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined;
|
||||
|
||||
return new sequelizeErrors.ForeignKeyConstraintError({
|
||||
reltype: String(errCode) === '1451' ? 'parent' : 'child',
|
||||
table: match ? match[4] : undefined,
|
||||
fields,
|
||||
value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined,
|
||||
index: match ? match[2] : undefined,
|
||||
parent: err
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
return new sequelizeErrors.DatabaseError(err);
|
||||
}
|
||||
}
|
||||
|
||||
handleShowIndexesQuery(data) {
|
||||
// Group by index name, and collect all fields
|
||||
data = data.reduce((acc, item) => {
|
||||
if (!(item.Key_name in acc)) {
|
||||
acc[item.Key_name] = item;
|
||||
item.fields = [];
|
||||
}
|
||||
|
||||
acc[item.Key_name].fields[item.Seq_in_index - 1] = {
|
||||
attribute: item.Column_name,
|
||||
length: item.Sub_part || undefined,
|
||||
order: item.Collation === 'A' ? 'ASC' : undefined
|
||||
};
|
||||
delete item.column_name;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return _.map(data, item => ({
|
||||
primary: item.Key_name === 'PRIMARY',
|
||||
fields: item.fields,
|
||||
name: item.Key_name,
|
||||
tableName: item.Table,
|
||||
unique: item.Non_unique !== 1,
|
||||
type: item.Index_type
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Query;
|
||||
module.exports.Query = Query;
|
||||
module.exports.default = Query;
|
||||
Reference in New Issue
Block a user