Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(statement_timeout): Support for PostgreSQL statement_timeout #8254

Closed
wants to merge 10 commits into from
4 changes: 4 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ const sequelize = new Sequelize('database', 'username', 'password', {
})
```

If you're using pg@^7.3.0, you can optionally provide a `statement_timeout` value in milliseconds in your `dialect` settings.
If provided, PostgreSQL will cancel any query after it exceeds your `statement_timeout` value. This is useful for running on systems
like Heroku where queries cannot exceed the maximum HTTP timeout of 30 seconds.

### MSSQL

The library for MSSQL is`tedious@^1.7.0` You'll just need to define the dialect:
Expand Down
7 changes: 5 additions & 2 deletions lib/dialects/postgres/connection-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ class ConnectionManager extends AbstractConnectionManager {
'binary',
// This should help with backends incorrectly considering idle clients to be dead and prematurely disconnecting them.
// this feature has been added in pg module v6.0.0, check pg/CHANGELOG.md
'keepAlive'
'keepAlive',
// Abort any statement that takes more than the specified number of milliseconds, starting from the time the command arrives at the server from the client.
// see also [https://www.postgresql.org/docs/9.6/static/runtime-config-client.html]
'statement_timeout'
]));
}

Expand Down Expand Up @@ -155,7 +158,7 @@ class ConnectionManager extends AbstractConnectionManager {

return new Promise((resolve, reject) => connection.query(query, (error, result) => error ? reject(error) : resolve(result))).then(results => {
const result = Array.isArray(results) ? results.pop() : results;

for (const row of result.rows) {
let type;
if (row.typname === 'geometry') {
Expand Down
37 changes: 35 additions & 2 deletions test/integration/dialects/postgres/connection-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
const chai = require('chai'),
expect = chai.expect,
Support = require(__dirname + '/../../support'),
dialect = Support.getTestDialect(),
DataTypes = require(__dirname + '/../../../../lib/data-types'),
dialect = Support.getTestDialect(),
_ = require('lodash');

if (dialect.match(/^postgres/)) {
if ( dialect.match(/^postgres/) ) {
describe('[POSTGRES] Sequelize', () => {
function checkTimezoneParsing(baseOptions) {
const options = _.extend({}, baseOptions, { timezone: 'Asia/Kolkata', timestamps: true });
Expand All @@ -31,5 +31,38 @@ if (dialect.match(/^postgres/)) {
DataTypes.HSTORE.types.postgres.array_oids = [];
return checkTimezoneParsing(this.sequelize.options);
});

if ( process.env.DIALECT === 'postgres' ) {

// This test will only run with node-pg 7.3.0 or greater. node-pg requires node-pg-native 2.0.0 or greater.
// node-pg-native 2.0.0 breaks a bunch of tests.
it.skip('should properly pass statement_timeout to postgres', function() {
const options = _.extend({}, this.sequelize.options, { dialectOptions: { 'statement_timeout': 10 } });
const sequelize = Support.createSequelizeInstance(options);

return sequelize.query('SHOW statement_timeout')
.then( result => {
const timeout = _.get( result, '[0].statement_timeout' );
expect(timeout).to.equal('10ms');
});
});

// This test will only run with node-pg 7.3.0 or greater. node-pg requires node-pg-native 2.0.0 or greater.
// node-pg-native 2.0.0 breaks a bunch of tests.
it.skip('should properly error when a statement is cancelled due to a statement_timeout', function() {
const self = this;
const options = _.extend({}, this.sequelize.options, { dialectOptions: { 'statement_timeout': 10 } });
const sequelize = Support.createSequelizeInstance(options);

return sequelize.query('SELECT pg_sleep(1)')
.then( () => {
expect.fail('Postgres should have cancelled the query with an error');
})
.catch( self.sequelize.DatabaseError, error => {
expect(error.parent.code).to.equal('57014'); // postgres query_cancelled error
});
});
}

});
}