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

Commit 503e9823 by Sushant Committed by GitHub

feat(model) decrement method (#9169)

1 parent 969978d6
......@@ -2923,30 +2923,33 @@ class Model {
/**
* Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a
* ```sql
* SET column = column + X WHERE foo = 'bar'
* ``` SET column = column + X WHERE foo = 'bar' ``` query. To get the correct value after an increment into the Instance you should do a reload.
*
* ```js
* // increment number by 1
* Model.increment('number', { where: { foo: 'bar' });
*
* // increment number and count by 2
* Model.increment(['number', 'count'], { by: 2, where: { foo: 'bar' } });
*
* // increment answer by 42, and decrement tries by 1.
* // `by` is ignored, since each column has its own value
* Model.increment({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } });
* ```
* query. To get the correct value after an increment into the Instance you should do a reload.
*
*```js
* Model.increment('number', { where: { foo: 'bar' }) // increment number by 1
* Model.increment(['number', 'count'], { by: 2, where: { foo: 'bar' } }) // increment number and count by 2
* Model.increment({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } }) // increment answer by 42, and decrement tries by 1.
* // `by` is ignored, since each column has its own value
* ```
*
* @see {@link Model#reload}
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given.
* @param {Object} options
* @param {Object} options.where
* @param {Integer} [options.by=1] The number to increment by
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
* @param {String} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only)
*
* @return {Promise<this>}
*/
* @see {@link Model#reload}
*
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given.
* @param {Object} options
* @param {Object} options.where
* @param {Integer} [options.by=1] The number to increment by
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
* @param {String} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only)
*
* @return {Promise<this>}
*/
static increment(fields, options) {
options = options || {};
......@@ -3009,6 +3012,36 @@ class Model {
});
}
/**
* Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a
* ```sql SET column = column - X WHERE foo = 'bar'``` query. To get the correct value after a decrement into the Instance you should do a reload.
*
*```js
// decrement number by 1
* Model.decrement('number', { where: { foo: 'bar' });
*
* // decrement number and count by 2
* Model.decrement(['number', 'count'], { by: 2, where: { foo: 'bar' } });
*
* // decrement answer by 42, and decrement tries by -1.
* // `by` is ignored, since each column has its own value
* Model.decrement({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } });
* ```
*
* @see {@link Model#increment}
* @see {@link Model#reload}
* @since 4.36.0
* @return {Promise<this>}
*/
static decrement(fields, options) {
options = _.defaults({ increment: false }, options, {
by: 1
});
return this.increment(fields, options);
}
static _optionsMustContainWhere(options) {
assert(options && options.where, 'Missing where attribute in the options parameter');
assert(_.isPlainObject(options.where) || _.isArray(options.where) || options.where instanceof Utils.SequelizeMethod,
......
......@@ -2843,220 +2843,4 @@ describe(Support.getTestDialectTeaser('Model'), () => {
})).to.be.rejectedWith(Promise.AggregateError);
});
});
describe('increment', () => {
beforeEach(function() {
this.User = this.sequelize.define('User', {
id: { type: DataTypes.INTEGER, primaryKey: true },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
cNumber: { type: DataTypes.INTEGER, field: 'c_number'}
});
const self = this;
return this.User.sync({ force: true }).then(() => {
return self.User.bulkCreate([{
id: 1,
aNumber: 0,
bNumber: 0
}, {
id: 2,
aNumber: 0,
bNumber: 0
}, {
id: 3,
aNumber: 0,
bNumber: 0
}, {
id: 4,
aNumber: 0,
bNumber: 0,
cNumber: 0
}]);
});
});
it('supports where conditions', function() {
const self = this;
return this.User.findById(1).then(() => {
return self.User.increment(['aNumber'], { by: 2, where: { id: 1 } }).then(() => {
return self.User.findById(2).then(user3 => {
expect(user3.aNumber).to.be.equal(0);
});
});
});
});
it('uses correct column names for where conditions', function() {
const self = this;
return this.User.increment(['aNumber'], {by: 2, where: {cNumber: 0}}).then(() => {
return self.User.findById(4).then(user4 => {
expect(user4.aNumber).to.be.equal(2);
});
});
});
it('should still work right with other concurrent increments', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.sequelize.Promise.all([
self.User.increment(['aNumber'], { by: 2, where: {} }),
self.User.increment(['aNumber'], { by: 2, where: {} }),
self.User.increment(['aNumber'], { by: 2, where: {} })
]).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 6);
}
});
});
});
});
it('with array', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.User.increment(['aNumber'], { by: 2, where: {} }).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 2);
}
});
});
});
});
it('with single field', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.User.increment('aNumber', { by: 2, where: {} }).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 2);
}
});
});
});
});
it('with single field and no value', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.User.increment('aNumber', { where: {}}).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 1);
}
});
});
});
});
it('with key value pair', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.User.increment({ 'aNumber': 1, 'bNumber': 2 }, { where: { }}).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 1);
expect(bUsers[i].bNumber).to.equal(aUsers[i].bNumber + 2);
}
});
});
});
});
it('should still work right with other concurrent updates', function() {
const self = this;
return this.User.findAll().then(aUsers => {
return self.User.update({ 'aNumber': 2 }, { where: {} }).then(() => {
return self.User.increment(['aNumber'], { by: 2, where: {} }).then(() => {
return self.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(aUsers[i].aNumber + 4);
}
});
});
});
});
});
it('with timestamps set to true', function() {
const User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true });
let oldDate;
return User.sync({ force: true }).bind(this).then(() => {
return User.create({aNumber: 1});
}).then(function(user) {
oldDate = user.updatedAt;
this.clock.tick(1000);
return User.increment('aNumber', {by: 1, where: {}});
}).then(() => {
return expect(User.findById(1)).to.eventually.have.property('updatedAt').afterTime(oldDate);
});
});
it('with timestamps set to true and options.silent set to true', function() {
const User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true });
let oldDate;
return User.sync({ force: true }).bind(this).then(() => {
return User.create({aNumber: 1});
}).then(function(user) {
oldDate = user.updatedAt;
this.clock.tick(1000);
return User.increment('aNumber', {by: 1, silent: true, where: { }});
}).then(() => {
return expect(User.findById(1)).to.eventually.have.property('updatedAt').equalTime(oldDate);
});
});
it('should work with scopes', function() {
const User = this.sequelize.define('User', {
aNumber: DataTypes.INTEGER,
name: DataTypes.STRING
}, {
scopes: {
jeff: {
where: {
name: 'Jeff'
}
}
}
});
return User.sync({ force: true }).then(() => {
return User.bulkCreate([
{
aNumber: 1,
name: 'Jeff'
},
{
aNumber: 3,
name: 'Not Jeff'
}
]);
}).then(() => {
return User.scope('jeff').increment('aNumber', {
});
}).then(() => {
return User.scope('jeff').findOne();
}).then(jeff => {
expect(jeff.aNumber).to.equal(2);
}).then(() => {
return User.findOne({
where: {
name: 'Not Jeff'
}
});
}).then(notJeff => {
expect(notJeff.aNumber).to.equal(3);
});
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require(__dirname + '/../support'),
DataTypes = require(__dirname + '/../../../lib/data-types'),
sinon = require('sinon');
describe(Support.getTestDialectTeaser('Model'), () => {
before(function() {
this.clock = sinon.useFakeTimers();
});
after(function() {
this.clock.restore();
});
beforeEach(function() {
this.User = this.sequelize.define('User', {
id: { type: DataTypes.INTEGER, primaryKey: true },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
cNumber: { type: DataTypes.INTEGER, field: 'c_number'}
});
return this.User.sync({ force: true }).then(() => {
return this.User.bulkCreate([{
id: 1,
aNumber: 0,
bNumber: 0
}, {
id: 2,
aNumber: 0,
bNumber: 0
}, {
id: 3,
aNumber: 0,
bNumber: 0
}, {
id: 4,
aNumber: 0,
bNumber: 0,
cNumber: 0
}]);
});
});
[
'increment',
'decrement'
].forEach(method => {
describe(method, () => {
before(function () {
this.assert = (increment, decrement) => {
return method === 'increment' ? increment : decrement;
};
});
it('supports where conditions', function() {
return this.User.findById(1).then(() => {
return this.User[method](['aNumber'], { by: 2, where: { id: 1 } }).then(() => {
return this.User.findById(2).then(user3 => {
expect(user3.aNumber).to.be.equal(this.assert(0, 0));
});
});
});
});
it('uses correct column names for where conditions', function() {
return this.User[method](['aNumber'], {by: 2, where: {cNumber: 0}}).then(() => {
return this.User.findById(4).then(user4 => {
expect(user4.aNumber).to.be.equal(this.assert(2, -2));
});
});
});
it('should still work right with other concurrent increments', function() {
return this.User.findAll().then(aUsers => {
return this.sequelize.Promise.all([
this.User[method](['aNumber'], { by: 2, where: {} }),
this.User[method](['aNumber'], { by: 2, where: {} }),
this.User[method](['aNumber'], { by: 2, where: {} })
]).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 6, aUsers[i].aNumber - 6));
}
});
});
});
});
it('with array', function() {
return this.User.findAll().then(aUsers => {
return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2));
}
});
});
});
});
it('with single field', function() {
return this.User.findAll().then(aUsers => {
return this.User[method]('aNumber', { by: 2, where: {} }).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2));
}
});
});
});
});
it('with single field and no value', function() {
return this.User.findAll().then(aUsers => {
return this.User[method]('aNumber', { where: {}}).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1));
}
});
});
});
});
it('with key value pair', function() {
return this.User.findAll().then(aUsers => {
return this.User[method]({ 'aNumber': 1, 'bNumber': 2 }, { where: { }}).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1));
expect(bUsers[i].bNumber).to.equal(this.assert(aUsers[i].bNumber + 2, aUsers[i].bNumber - 2));
}
});
});
});
});
it('should still work right with other concurrent updates', function() {
return this.User.findAll().then(aUsers => {
return this.User.update({ 'aNumber': 2 }, { where: {} }).then(() => {
return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => {
return this.User.findAll().then(bUsers => {
for (let i = 0; i < bUsers.length; i++) {
// for decrement 2 - 2 = 0
expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 4, aUsers[i].aNumber));
}
});
});
});
});
});
it('with timestamps set to true', function() {
const User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true });
let oldDate;
return User.sync({ force: true }).bind(this).then(() => {
return User.create({aNumber: 1});
}).then(function(user) {
oldDate = user.updatedAt;
this.clock.tick(1000);
return User[method]('aNumber', {by: 1, where: {}});
}).then(() => {
return expect(User.findById(1)).to.eventually.have.property('updatedAt').afterTime(oldDate);
});
});
it('with timestamps set to true and options.silent set to true', function() {
const User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true });
let oldDate;
return User.sync({ force: true }).bind(this).then(() => {
return User.create({aNumber: 1});
}).then(function(user) {
oldDate = user.updatedAt;
this.clock.tick(1000);
return User[method]('aNumber', {by: 1, silent: true, where: { }});
}).then(() => {
return expect(User.findById(1)).to.eventually.have.property('updatedAt').equalTime(oldDate);
});
});
it('should work with scopes', function() {
const User = this.sequelize.define('User', {
aNumber: DataTypes.INTEGER,
name: DataTypes.STRING
}, {
scopes: {
jeff: {
where: {
name: 'Jeff'
}
}
}
});
return User.sync({ force: true }).then(() => {
return User.bulkCreate([
{
aNumber: 1,
name: 'Jeff'
},
{
aNumber: 3,
name: 'Not Jeff'
}
]);
}).then(() => {
return User.scope('jeff')[method]('aNumber', {});
}).then(() => {
return User.scope('jeff').findOne();
}).then(jeff => {
expect(jeff.aNumber).to.equal(this.assert(2, 0));
}).then(() => {
return User.findOne({
where: {
name: 'Not Jeff'
}
});
}).then(notJeff => {
expect(notJeff.aNumber).to.equal(this.assert(3, 3));
});
});
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!