Skip to content

Commit 10c34e3

Browse files
fix(query): don't prepare options & sql for every retry (#10498)
1 parent e5c0d78 commit 10c34e3

File tree

2 files changed

+102
-72
lines changed

2 files changed

+102
-72
lines changed

lib/sequelize.js

Lines changed: 71 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -460,22 +460,49 @@ class Sequelize {
460460

461461
query(sql, options) {
462462
options = Object.assign({}, this.options.query, options);
463-
const retryOptions = Object.assign({}, this.options.retry, options.retry || {});
464463

465-
let bindParameters;
464+
if (options.instance && !options.model) {
465+
options.model = options.instance.constructor;
466+
}
466467

467-
return Promise.resolve(retry(retryParameters => Promise.try(() => {
468-
const isFirstTry = retryParameters.current === 1;
468+
if (!options.instance && !options.model) {
469+
options.raw = true;
470+
}
469471

470-
if (options.instance && !options.model) {
471-
options.model = options.instance.constructor;
472-
}
472+
// map raw fields to model attributes
473+
if (options.mapToModel) {
474+
options.fieldMap = _.get(options, 'model.fieldAttributeMap', {});
475+
}
473476

474-
// map raw fields to model attributes
475-
if (options.mapToModel) {
476-
options.fieldMap = _.get(options, 'model.fieldAttributeMap', {});
477+
options = _.defaults(options, {
478+
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
479+
searchPath: this.options.hasOwnProperty('searchPath') ? this.options.searchPath : 'DEFAULT'
480+
});
481+
482+
if (!options.type) {
483+
if (options.model || options.nest || options.plain) {
484+
options.type = QueryTypes.SELECT;
485+
} else {
486+
options.type = QueryTypes.RAW;
477487
}
488+
}
478489

490+
//if dialect doesn't support search_path or dialect option
491+
//to prepend searchPath is not true delete the searchPath option
492+
if (
493+
!this.dialect.supports.searchPath ||
494+
!this.options.dialectOptions ||
495+
!this.options.dialectOptions.prependSearchPath ||
496+
options.supportsSearchPath === false
497+
) {
498+
delete options.searchPath;
499+
} else if (!options.searchPath) {
500+
//if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true)
501+
//then set to DEFAULT if none is provided
502+
options.searchPath = 'DEFAULT';
503+
}
504+
505+
return Promise.try(() => {
479506
if (typeof sql === 'object') {
480507
if (sql.values !== undefined) {
481508
if (options.replacements !== undefined) {
@@ -498,10 +525,6 @@ class Sequelize {
498525

499526
sql = sql.trim();
500527

501-
if (!options.instance && !options.model) {
502-
options.raw = true;
503-
}
504-
505528
if (options.replacements && options.bind) {
506529
throw new Error('Both `replacements` and `bind` cannot be set at the same time');
507530
}
@@ -514,71 +537,49 @@ class Sequelize {
514537
}
515538
}
516539

540+
let bindParameters;
541+
517542
if (options.bind) {
518-
const bindSql = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect);
519-
sql = bindSql[0];
520-
bindParameters = bindSql[1];
543+
[sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect);
521544
}
522545

523-
options = _.defaults(options, {
524-
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
525-
searchPath: this.options.hasOwnProperty('searchPath') ? this.options.searchPath : 'DEFAULT'
526-
});
546+
const retryOptions = Object.assign({}, this.options.retry, options.retry || {});
527547

528-
if (options.transaction === undefined && Sequelize._cls) {
529-
options.transaction = Sequelize._cls.get('transaction');
530-
}
548+
return Promise.resolve(retry(retryParameters => Promise.try(() => {
549+
const isFirstTry = retryParameters.current === 1;
531550

532-
if (!options.type) {
533-
if (options.model || options.nest || options.plain) {
534-
options.type = QueryTypes.SELECT;
535-
} else {
536-
options.type = QueryTypes.RAW;
551+
if (isFirstTry && this.test._trackRunningQueries) {
552+
this.test._runningQueries++;
537553
}
538-
}
539-
540-
if (options.transaction && options.transaction.finished) {
541-
const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the \'sql\' property of this error)`);
542-
error.sql = sql;
543-
return Promise.reject(error);
544-
}
545-
546-
if (isFirstTry && this.test._trackRunningQueries) {
547-
this.test._runningQueries++;
548-
}
549-
550-
//if dialect doesn't support search_path or dialect option
551-
//to prepend searchPath is not true delete the searchPath option
552-
if (
553-
!this.dialect.supports.searchPath ||
554-
!this.options.dialectOptions ||
555-
!this.options.dialectOptions.prependSearchPath ||
556-
options.supportsSearchPath === false
557-
) {
558-
delete options.searchPath;
559-
} else if (!options.searchPath) {
560-
//if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true)
561-
//then set to DEFAULT if none is provided
562-
options.searchPath = 'DEFAULT';
563-
}
564554

565-
return options.transaction
566-
? options.transaction.connection
567-
: this.connectionManager.getConnection(options);
568-
}).then(connection => {
569-
const query = new this.dialect.Query(connection, this, options);
555+
if (options.transaction === undefined && Sequelize._cls) {
556+
options.transaction = Sequelize._cls.get('transaction');
557+
}
570558

571-
return query.run(sql, bindParameters)
572-
.finally(() => {
573-
if (this.test._trackRunningQueries) {
574-
this.test._runningQueries--;
575-
}
559+
if (options.transaction && options.transaction.finished) {
560+
const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the \'sql\' property of this error)`);
561+
error.sql = sql;
562+
throw error;
563+
}
576564

577-
if (!options.transaction) {
578-
return this.connectionManager.releaseConnection(connection);
579-
}
580-
});
581-
}), retryOptions));
565+
return options.transaction
566+
? options.transaction.connection
567+
: this.connectionManager.getConnection(options);
568+
}).then(connection => {
569+
const query = new this.dialect.Query(connection, this, options);
570+
571+
return query.run(sql, bindParameters)
572+
.finally(() => {
573+
if (!options.transaction) {
574+
return this.connectionManager.releaseConnection(connection);
575+
}
576+
});
577+
}), retryOptions)).finally(() => {
578+
if (this.test._trackRunningQueries) {
579+
this.test._runningQueries--;
580+
}
581+
});
582+
});
582583
}
583584

584585
/**

test/integration/sequelize.test.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,15 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
225225
describe('query', () => {
226226
afterEach(function() {
227227
this.sequelize.options.quoteIdentifiers = true;
228-
229228
console.log.restore && console.log.restore();
230229
});
231230

232231
beforeEach(function() {
233232
this.User = this.sequelize.define('User', {
234-
username: DataTypes.STRING,
233+
username: {
234+
type: DataTypes.STRING,
235+
unique: true
236+
},
235237
emailAddress: {
236238
type: DataTypes.STRING,
237239
field: 'email_address'
@@ -271,6 +273,33 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
271273
});
272274
});
273275

276+
describe('retry', () => {
277+
it('properly bind parameters on extra retries', function() {
278+
const payload = {
279+
username: 'test',
280+
createdAt: '2010-10-10 00:00:00',
281+
updatedAt: '2010-10-10 00:00:00'
282+
};
283+
284+
const spy = sinon.spy();
285+
286+
return expect(this.User.create(payload).then(() => this.sequelize.query(`
287+
INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt);
288+
`, {
289+
bind: payload,
290+
logging: spy,
291+
retry: {
292+
max: 3,
293+
match: [
294+
/Validation/
295+
]
296+
}
297+
}))).to.be.rejectedWith(Sequelize.UniqueConstraintError).then(() => {
298+
expect(spy.callCount).to.eql(3);
299+
});
300+
});
301+
});
302+
274303
describe('logging', () => {
275304
it('executes a query with global benchmarking option and default logger', () => {
276305
const logger = sinon.spy(console, 'log');

0 commit comments

Comments
 (0)