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

Commit 10c34e3a by Sushant Committed by GitHub

fix(query): don't prepare options & sql for every retry (#10498)

1 parent e5c0d786
...@@ -460,22 +460,49 @@ class Sequelize { ...@@ -460,22 +460,49 @@ class Sequelize {
query(sql, options) { query(sql, options) {
options = Object.assign({}, this.options.query, options); options = Object.assign({}, this.options.query, options);
const retryOptions = Object.assign({}, this.options.retry, options.retry || {});
let bindParameters; if (options.instance && !options.model) {
options.model = options.instance.constructor;
}
return Promise.resolve(retry(retryParameters => Promise.try(() => { if (!options.instance && !options.model) {
const isFirstTry = retryParameters.current === 1; options.raw = true;
}
if (options.instance && !options.model) { // map raw fields to model attributes
options.model = options.instance.constructor; if (options.mapToModel) {
} options.fieldMap = _.get(options, 'model.fieldAttributeMap', {});
}
// map raw fields to model attributes options = _.defaults(options, {
if (options.mapToModel) { logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); searchPath: this.options.hasOwnProperty('searchPath') ? this.options.searchPath : 'DEFAULT'
});
if (!options.type) {
if (options.model || options.nest || options.plain) {
options.type = QueryTypes.SELECT;
} else {
options.type = QueryTypes.RAW;
} }
}
//if dialect doesn't support search_path or dialect option
//to prepend searchPath is not true delete the searchPath option
if (
!this.dialect.supports.searchPath ||
!this.options.dialectOptions ||
!this.options.dialectOptions.prependSearchPath ||
options.supportsSearchPath === false
) {
delete options.searchPath;
} else if (!options.searchPath) {
//if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true)
//then set to DEFAULT if none is provided
options.searchPath = 'DEFAULT';
}
return Promise.try(() => {
if (typeof sql === 'object') { if (typeof sql === 'object') {
if (sql.values !== undefined) { if (sql.values !== undefined) {
if (options.replacements !== undefined) { if (options.replacements !== undefined) {
...@@ -498,10 +525,6 @@ class Sequelize { ...@@ -498,10 +525,6 @@ class Sequelize {
sql = sql.trim(); sql = sql.trim();
if (!options.instance && !options.model) {
options.raw = true;
}
if (options.replacements && options.bind) { if (options.replacements && options.bind) {
throw new Error('Both `replacements` and `bind` cannot be set at the same time'); throw new Error('Both `replacements` and `bind` cannot be set at the same time');
} }
...@@ -514,71 +537,49 @@ class Sequelize { ...@@ -514,71 +537,49 @@ class Sequelize {
} }
} }
let bindParameters;
if (options.bind) { if (options.bind) {
const bindSql = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect);
sql = bindSql[0];
bindParameters = bindSql[1];
} }
options = _.defaults(options, { const retryOptions = Object.assign({}, this.options.retry, options.retry || {});
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
searchPath: this.options.hasOwnProperty('searchPath') ? this.options.searchPath : 'DEFAULT'
});
if (options.transaction === undefined && Sequelize._cls) { return Promise.resolve(retry(retryParameters => Promise.try(() => {
options.transaction = Sequelize._cls.get('transaction'); const isFirstTry = retryParameters.current === 1;
}
if (!options.type) { if (isFirstTry && this.test._trackRunningQueries) {
if (options.model || options.nest || options.plain) { this.test._runningQueries++;
options.type = QueryTypes.SELECT;
} else {
options.type = QueryTypes.RAW;
} }
}
if (options.transaction && options.transaction.finished) {
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)`);
error.sql = sql;
return Promise.reject(error);
}
if (isFirstTry && this.test._trackRunningQueries) {
this.test._runningQueries++;
}
//if dialect doesn't support search_path or dialect option
//to prepend searchPath is not true delete the searchPath option
if (
!this.dialect.supports.searchPath ||
!this.options.dialectOptions ||
!this.options.dialectOptions.prependSearchPath ||
options.supportsSearchPath === false
) {
delete options.searchPath;
} else if (!options.searchPath) {
//if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true)
//then set to DEFAULT if none is provided
options.searchPath = 'DEFAULT';
}
return options.transaction if (options.transaction === undefined && Sequelize._cls) {
? options.transaction.connection options.transaction = Sequelize._cls.get('transaction');
: this.connectionManager.getConnection(options); }
}).then(connection => {
const query = new this.dialect.Query(connection, this, options);
return query.run(sql, bindParameters) if (options.transaction && options.transaction.finished) {
.finally(() => { 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)`);
if (this.test._trackRunningQueries) { error.sql = sql;
this.test._runningQueries--; throw error;
} }
if (!options.transaction) { return options.transaction
return this.connectionManager.releaseConnection(connection); ? options.transaction.connection
} : this.connectionManager.getConnection(options);
}); }).then(connection => {
}), retryOptions)); const query = new this.dialect.Query(connection, this, options);
return query.run(sql, bindParameters)
.finally(() => {
if (!options.transaction) {
return this.connectionManager.releaseConnection(connection);
}
});
}), retryOptions)).finally(() => {
if (this.test._trackRunningQueries) {
this.test._runningQueries--;
}
});
});
} }
/** /**
......
...@@ -225,13 +225,15 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { ...@@ -225,13 +225,15 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
describe('query', () => { describe('query', () => {
afterEach(function() { afterEach(function() {
this.sequelize.options.quoteIdentifiers = true; this.sequelize.options.quoteIdentifiers = true;
console.log.restore && console.log.restore(); console.log.restore && console.log.restore();
}); });
beforeEach(function() { beforeEach(function() {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING, username: {
type: DataTypes.STRING,
unique: true
},
emailAddress: { emailAddress: {
type: DataTypes.STRING, type: DataTypes.STRING,
field: 'email_address' field: 'email_address'
...@@ -271,6 +273,33 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { ...@@ -271,6 +273,33 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
}); });
}); });
describe('retry', () => {
it('properly bind parameters on extra retries', function() {
const payload = {
username: 'test',
createdAt: '2010-10-10 00:00:00',
updatedAt: '2010-10-10 00:00:00'
};
const spy = sinon.spy();
return expect(this.User.create(payload).then(() => this.sequelize.query(`
INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt);
`, {
bind: payload,
logging: spy,
retry: {
max: 3,
match: [
/Validation/
]
}
}))).to.be.rejectedWith(Sequelize.UniqueConstraintError).then(() => {
expect(spy.callCount).to.eql(3);
});
});
});
describe('logging', () => { describe('logging', () => {
it('executes a query with global benchmarking option and default logger', () => { it('executes a query with global benchmarking option and default logger', () => {
const logger = sinon.spy(console, 'log'); const logger = sinon.spy(console, 'log');
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!