不要怂,就是干,撸起袖子干!

Commit 5b1dee60 by Jan Aagaard Meier

Merge pull request #2576 from DavidTPate/feature/mysql-connection-failure-errors

Add Connection Specific Errors
2 parents b07462e0 0de0fa23
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var AbstractConnectionManager = require('../abstract/connection-manager') var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise'); , Promise = require('../../promise')
, sequelizeErrors = require('../../errors');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -44,7 +45,7 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -44,7 +45,7 @@ ConnectionManager.prototype.connect = function(config) {
var connection = new self.lib(); var connection = new self.lib();
connection.connect(connectionConfig); connection.connect(connectionConfig);
connection.on('error', function(err) { connection.on('error', function(err) {
return reject(err); return reject(new sequelizeErrors.ConnectionError(err));
}); });
connection.on('connect', function() { connection.on('connect', function() {
return resolve(connection); return resolve(connection);
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var AbstractConnectionManager = require('../abstract/connection-manager') var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise'); , Promise = require('../../promise')
, sequelizeErrors = require('../../errors');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -40,19 +41,29 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -40,19 +41,29 @@ ConnectionManager.prototype.connect = function(config) {
connection.connect(function(err) { connection.connect(function(err) {
if (err) { if (err) {
switch (err.code) { if (err.code) {
case 'ECONNREFUSED': switch (err.code) {
case 'ER_ACCESS_D2ENIED_ERROR': case 'ECONNREFUSED':
reject('Failed to authenticate for MySQL. Please double check your settings.'); reject(new sequelizeErrors.ConnectionRefusedError(err));
break; break;
case 'ENOTFOUND': case 'ER_ACCESS_DENIED_ERROR':
case 'EHOSTUNREACH': reject(new sequelizeErrors.AccessDeniedError(err));
case 'EINVAL': break;
reject('Failed to find MySQL server. Please double check your settings.'); case 'ENOTFOUND':
break; reject(new sequelizeErrors.HostNotFoundError(err));
default: break;
reject(err); case 'EHOSTUNREACH':
break; reject(new sequelizeErrors.HostNotReachableError(err));
break;
case 'EINVAL':
reject(new sequelizeErrors.InvalidConnectionError(err));
break;
default:
reject(new sequelizeErrors.ConnectionError(err));
break;
}
} else {
reject(new sequelizeErrors.ConnectionError(err));
} }
return; return;
...@@ -89,7 +100,7 @@ ConnectionManager.prototype.disconnect = function(connection) { ...@@ -89,7 +100,7 @@ ConnectionManager.prototype.disconnect = function(connection) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
connection.end(function(err) { connection.end(function(err) {
if (err) return reject(err); if (err) return reject(new sequelizeErrors.ConnectionError(err));
resolve(); resolve();
}); });
}); });
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var AbstractConnectionManager = require('../abstract/connection-manager') var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise'); , Promise = require('../../promise')
, sequelizeErrors = require('../../errors');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
AbstractConnectionManager.call(this, dialect, sequelize); AbstractConnectionManager.call(this, dialect, sequelize);
...@@ -56,19 +57,23 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -56,19 +57,23 @@ ConnectionManager.prototype.connect = function(config) {
if (err.code) { if (err.code) {
switch (err.code) { switch (err.code) {
case 'ECONNREFUSED': case 'ECONNREFUSED':
reject(new Error('Failed to authenticate for PostgresSQL. Please double check your settings.')); reject(new sequelizeErrors.ConnectionRefusedError(err));
break; break;
case 'ENOTFOUND': case 'ENOTFOUND':
reject(new sequelizeErrors.HostNotFoundError(err));
break;
case 'EHOSTUNREACH': case 'EHOSTUNREACH':
reject(new sequelizeErrors.HostNotReachableError(err));
break;
case 'EINVAL': case 'EINVAL':
reject(new Error('Failed to find PostgresSQL server. Please double check your settings.')); reject(new sequelizeErrors.InvalidConnectionError(err));
break; break;
default: default:
reject(err); reject(new sequelizeErrors.ConnectionError(err));
break; break;
} }
} else { } else {
reject(new Error(err.message)); reject(new sequelizeErrors.ConnectionError(err));
} }
return; return;
} }
...@@ -79,7 +84,7 @@ ConnectionManager.prototype.connect = function(config) { ...@@ -79,7 +84,7 @@ ConnectionManager.prototype.connect = function(config) {
// If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted // If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
connection.on('end', function () { connection.on('end', function () {
if (!responded) { if (!responded) {
reject(new Error('Connection timed out')); reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out')));
} }
}); });
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
var AbstractConnectionManager = require('../abstract/connection-manager') var AbstractConnectionManager = require('../abstract/connection-manager')
, ConnectionManager , ConnectionManager
, Utils = require('../../utils') , Utils = require('../../utils')
, Promise = require('../../promise'); , Promise = require('../../promise')
, sequelizeErrors = require('../../errors');
ConnectionManager = function(dialect, sequelize) { ConnectionManager = function(dialect, sequelize) {
this.sequelize = sequelize; this.sequelize = sequelize;
...@@ -29,8 +30,9 @@ ConnectionManager.prototype.getConnection = function(options) { ...@@ -29,8 +30,9 @@ ConnectionManager.prototype.getConnection = function(options) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
self.connections[options.uuid] = new self.lib.Database(self.sequelize.options.storage || self.sequelize.options.host || ':memory:', function(err) { self.connections[options.uuid] = new self.lib.Database(self.sequelize.options.storage || self.sequelize.options.host || ':memory:', function(err) {
if (err) { if (err) {
if (err.code === 'SQLITE_CANTOPEN') return reject('Failed to find SQL server. Please double check your settings.'); console.log(err);
return reject(err); if (err.code === 'SQLITE_CANTOPEN') return reject(new sequelizeErrors.ConnectionError(err));
return reject(new sequelizeErrors.ConnectionError(err));
} }
resolve(self.connections[options.uuid]); resolve(self.connections[options.uuid]);
}); });
......
...@@ -184,3 +184,89 @@ error.ValidationErrorItem = function(message, type, path, value) { ...@@ -184,3 +184,89 @@ error.ValidationErrorItem = function(message, type, path, value) {
this.path = path || null; this.path = path || null;
this.value = value || null; this.value = value || null;
}; };
/**
* A base class for all connection related errors.
* @extends BaseError
* @constructor
*/
error.ConnectionError = function (parent) {
error.BaseError.apply(this, [parent ? parent.message : '']);
this.name = 'SequelizeConnectionError';
this.parent = parent;
};
util.inherits(error.ConnectionError, error.BaseError);
/**
* The connection specific error which triggered this one
* @property parent
* @name parent
*/
error.ConnectionError.prototype.parent;
/**
* Thrown when a connection to a database is refused
* @extends ConnectionError
* @constructor
*/
error.ConnectionRefusedError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeConnectionRefusedError';
};
util.inherits(error.ConnectionRefusedError, error.ConnectionError);
/**
* Thrown when a connection to a database is refused due to insufficient privileges
* @extends ConnectionError
* @constructor
*/
error.AccessDeniedError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeAccessDeniedError';
};
util.inherits(error.AccessDeniedError, error.ConnectionError);
/**
* Thrown when a connection to a database has a hostname that was not found
* @extends ConnectionError
* @constructor
*/
error.HostNotFoundError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeHostNotFoundError';
};
util.inherits(error.HostNotFoundError, error.ConnectionError);
/**
* Thrown when a connection to a database has a hostname that was not reachable
* @extends ConnectionError
* @constructor
*/
error.HostNotReachableError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeHostNotReachableError';
};
util.inherits(error.HostNotReachableError, error.ConnectionError);
/**
* Thrown when a connection to a database has invalid values for any of the connection parameters
* @extends ConnectionError
* @constructor
*/
error.InvalidConnectionError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeInvalidConnectionError';
};
util.inherits(error.InvalidConnectionError, error.ConnectionError);
/**
* Thrown when a connection to a database times out
* @extends ConnectionError
* @constructor
*/
error.ConnectionTimedOutError = function (parent) {
error.ConnectionError.call(this, parent);
this.name = 'SequelizeConnectionTimedOutError';
};
util.inherits(error.ConnectionTimedOutError, error.ConnectionError);
...@@ -320,6 +320,55 @@ module.exports = (function() { ...@@ -320,6 +320,55 @@ module.exports = (function() {
sequelizeErrors.ForeignKeyConstraintError; sequelizeErrors.ForeignKeyConstraintError;
/** /**
* A base class for all connection related errors.
* @see {Errors#ConnectionError}
*/
Sequelize.prototype.ConnectionError = Sequelize.ConnectionError =
sequelizeErrors.ConnectionError;
/**
* Thrown when a connection to a database is refused
* @see {Errors#ConnectionRefusedError}
*/
Sequelize.prototype.ConnectionRefusedError = Sequelize.ConnectionRefusedError =
sequelizeErrors.ConnectionRefusedError;
/**
* Thrown when a connection to a database is refused due to insufficient access
* @see {Errors#AccessDeniedError}
*/
Sequelize.prototype.AccessDeniedError = Sequelize.AccessDeniedError =
sequelizeErrors.AccessDeniedError;
/**
* Thrown when a connection to a database has a hostname that was not found
* @see {Errors#HostNotFoundError}
*/
Sequelize.prototype.HostNotFoundError = Sequelize.HostNotFoundError =
sequelizeErrors.HostNotFoundError;
/**
* Thrown when a connection to a database has a hostname that was not reachable
* @see {Errors#HostNotReachableError}
*/
Sequelize.prototype.HostNotReachableError = Sequelize.HostNotReachableError =
sequelizeErrors.HostNotReachableError;
/**
* Thrown when a connection to a database has invalid values for any of the connection parameters
* @see {Errors#InvalidConnectionError}
*/
Sequelize.prototype.InvalidConnectionError = Sequelize.InvalidConnectionError =
sequelizeErrors.InvalidConnectionError;
/**
* Thrown when a connection to a database times out
* @see {Errors#ConnectionTimedOutError}
*/
Sequelize.prototype.ConnectionTimedOutError = Sequelize.ConnectionTimedOutError =
sequelizeErrors.ConnectionTimedOutError;
/**
* Returns the specified dialect. * Returns the specified dialect.
* *
* @return {String} The specified dialect. * @return {String} The specified dialect.
......
...@@ -9,33 +9,36 @@ chai.config.includeStack = true ...@@ -9,33 +9,36 @@ chai.config.includeStack = true
describe(Support.getTestDialectTeaser("Configuration"), function() { describe(Support.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() { describe('Connections problems should fail with a nice message', function() {
it("when we don't have the correct server details", function(done) { it("when we don't have the correct server details", function() {
// mysql is not properly supported due to the custom pooling system if (dialect === 'mariadb') {
if (dialect !== "postgres" && dialect !== "postgres-native") {
console.log('This dialect doesn\'t support me :(') console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster expect(true).to.be.true // Silence Buster
return done() return;
} }
var seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect}) var seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
seq.query('select 1 as hello').error(function(err) { if (dialect === 'sqlite') {
expect(err.message).to.match(/Failed to find (.*?) Please double check your settings\./) // SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors.
done() return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file')
}) } else {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.InvalidConnectionError, 'connect EINVAL')
}
}) })
it('when we don\'t have the correct login information', function(done) { it('when we don\'t have the correct login information', function() {
if (dialect !== "postgres" && dialect !== "postgres-native") { if (dialect === 'mariadb') {
console.log('This dialect doesn\'t support me :(') console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster expect(true).to.be.true // Silence Buster
return done() return;
} }
var seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect}) var seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
seq.query('select 1 as hello').error(function(err) { if (dialect === 'sqlite') {
expect(err.message).to.match(/^Failed to authenticate/) // SQLite doesn't require authentication and `select 1 as hello` is a valid query, so this should be fulfilled not rejected for it.
done() return expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled
}) } else {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.ConnectionRefusedError, 'connect ECONNREFUSED')
}
}) })
it('when we don\'t have a valid dialect.', function(done) { it('when we don\'t have a valid dialect.', function(done) {
......
...@@ -63,6 +63,62 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () { ...@@ -63,6 +63,62 @@ describe(Support.getTestDialectTeaser("Sequelize Errors"), function () {
expect(databaseError.name).to.equal('SequelizeDatabaseError'); expect(databaseError.name).to.equal('SequelizeDatabaseError');
expect(databaseError.message).to.equal('original database error message'); expect(databaseError.message).to.equal('original database error message');
}); });
it('ConnectionError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.ConnectionError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeConnectionError');
expect(connectionError.message).to.equal('original connection error message');
});
it('ConnectionRefusedError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.ConnectionRefusedError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeConnectionRefusedError');
expect(connectionError.message).to.equal('original connection error message');
});
it('AccessDeniedError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.AccessDeniedError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeAccessDeniedError');
expect(connectionError.message).to.equal('original connection error message');
});
it('HostNotFoundError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.HostNotFoundError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeHostNotFoundError');
expect(connectionError.message).to.equal('original connection error message');
});
it('HostNotReachableError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.HostNotReachableError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeHostNotReachableError');
expect(connectionError.message).to.equal('original connection error message');
});
it('InvalidConnectionError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.InvalidConnectionError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeInvalidConnectionError');
expect(connectionError.message).to.equal('original connection error message');
});
it('ConnectionTimedOutError should keep original message', function() {
var orig = new Error('original connection error message');
var connectionError = new Sequelize.ConnectionTimedOutError(orig);
expect(connectionError).to.have.property('parent');
expect(connectionError.name).to.equal('SequelizeConnectionTimedOutError');
expect(connectionError.message).to.equal('original connection error message');
});
}); });
describe('Constraint error', function () { describe('Constraint error', function () {
......
...@@ -116,11 +116,11 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -116,11 +116,11 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
expect(err.message).to.match(/Access denied for user/) expect(err.message).to.match(/Access denied for user/)
} else if (dialect === 'postgres') { } else if (dialect === 'postgres') {
expect( expect(
err.message.match(/Failed to authenticate for PostgresSQL/) || err.message.match(/connect ECONNREFUSED/) ||
err.message.match(/invalid port number/) err.message.match(/invalid port number/)
).to.be.ok ).to.be.ok
} else { } else {
expect(err.message).to.match(/Failed to authenticate/) expect(err.message).to.match(/connect ECONNREFUSED/)
} }
done() done()
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!