194 lines
5.1 KiB
JavaScript
194 lines
5.1 KiB
JavaScript
'use strict';
|
|
|
|
var Utils = require('../../utils')
|
|
, AbstractQuery = require('../abstract/query')
|
|
, uuid = require('node-uuid')
|
|
, sequelizeErrors = require('../../errors.js');
|
|
|
|
var Query = function(connection, sequelize, options) {
|
|
this.connection = connection;
|
|
this.instance = options.instance;
|
|
this.model = options.model;
|
|
this.sequelize = sequelize;
|
|
this.uuid = uuid.v4();
|
|
this.options = Utils._.extend({
|
|
logging: console.log,
|
|
plain: false,
|
|
raw: false
|
|
}, options || {});
|
|
|
|
this.checkLoggingOption();
|
|
};
|
|
|
|
Utils.inherit(Query, AbstractQuery);
|
|
Query.prototype.run = function(sql) {
|
|
var self = this;
|
|
this.sql = sql;
|
|
|
|
this.sequelize.log('Executing (' + (this.connection.uuid || 'default') + '): ' + this.sql, this.options);
|
|
|
|
var promise = new Utils.Promise(function(resolve, reject) {
|
|
self.connection.query(self.sql, function(err, results) {
|
|
if (err) {
|
|
err.sql = sql;
|
|
|
|
reject(self.formatError(err));
|
|
} else {
|
|
resolve(self.formatResults(results));
|
|
}
|
|
}).setMaxListeners(100);
|
|
});
|
|
|
|
return promise;
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
Query.prototype.formatResults = function(data) {
|
|
var result = this.instance;
|
|
|
|
if (this.isInsertQuery(data)) {
|
|
this.handleInsertQuery(data);
|
|
|
|
if (!this.instance) {
|
|
result = data[this.getInsertIdField()];
|
|
}
|
|
}
|
|
|
|
if (this.isSelectQuery()) {
|
|
result = this.handleSelectQuery(data);
|
|
} else if (this.isShowTablesQuery()) {
|
|
result = this.handleShowTablesQuery(data);
|
|
} else if (this.isDescribeQuery()) {
|
|
result = {};
|
|
|
|
data.forEach(function(_result) {
|
|
result[_result.Field] = {
|
|
type: _result.Type.toUpperCase(),
|
|
allowNull: (_result.Null === 'YES'),
|
|
defaultValue: _result.Default
|
|
};
|
|
});
|
|
} else if (this.isShowIndexesQuery()) {
|
|
result = this.handleShowIndexesQuery(data);
|
|
|
|
} else if (this.isCallQuery()) {
|
|
result = data[0];
|
|
} else if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) {
|
|
result = data.affectedRows;
|
|
} else if (this.isVersionQuery()) {
|
|
result = data[0].version;
|
|
} else if (this.isForeignKeysQuery()) {
|
|
result = data;
|
|
} else if (this.isRawQuery()) {
|
|
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
|
|
result = [data, data];
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
|
|
Query.prototype.formatError = function (err) {
|
|
var match;
|
|
|
|
switch (err.errno || err.code) {
|
|
case 1062:
|
|
match = err.message.match(/Duplicate entry '(.*)' for key '?((.|\s)*?)'?$/);
|
|
|
|
var values = match ? match[1].split('-') : undefined
|
|
, fields = {}
|
|
, message = 'Validation error'
|
|
, uniqueKey = this.model && this.model.uniqueKeys[match[2]];
|
|
|
|
if (!!uniqueKey) {
|
|
if (!!uniqueKey.msg) message = uniqueKey.msg;
|
|
fields = Utils._.zipObject(uniqueKey.fields, values);
|
|
} else {
|
|
fields[match[2]] = match[1];
|
|
}
|
|
|
|
var errors = [];
|
|
var self = this;
|
|
Utils._.forOwn(fields, function(value, field) {
|
|
errors.push(new sequelizeErrors.ValidationErrorItem(
|
|
self.getUniqueConstraintErrorMessage(field),
|
|
'unique violation', field, value));
|
|
});
|
|
|
|
return new sequelizeErrors.UniqueConstraintError({
|
|
message: message,
|
|
errors: errors,
|
|
parent: err,
|
|
fields: fields
|
|
});
|
|
|
|
case 1451:
|
|
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(?: ON .*)?\)$/);
|
|
|
|
return new sequelizeErrors.ForeignKeyConstraintError({
|
|
fields: null,
|
|
index: match ? match[3] : undefined,
|
|
parent: err
|
|
});
|
|
|
|
case 1452:
|
|
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(.*)\)$/);
|
|
|
|
return new sequelizeErrors.ForeignKeyConstraintError({
|
|
fields: null,
|
|
index: match ? match[1] : undefined,
|
|
parent: err
|
|
});
|
|
|
|
default:
|
|
return new sequelizeErrors.DatabaseError(err);
|
|
}
|
|
};
|
|
|
|
Query.prototype.handleShowIndexesQuery = function (data) {
|
|
// Group by index name, and collect all fields
|
|
data = Utils._.foldl(data, function (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 Utils._.map(data, function(item) {
|
|
return {
|
|
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;
|