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

Commit fc8fdef0 by Jozef Hartinger Committed by Jan Aagaard Meier

Support condition objects in utility functions #6666 (#6685)

https://github.com/sequelize/sequelize/issues/6666

Makes it possible to pass condition objects (e.g. {foo: 'bar'}) to Sequelize.fn(), Sequelize.cast().
This makes it easy to use conditional counts in Sequelize queries, e.g.

Sequelize.fn('sum', {name: 'Fred'}) which results in SUM(`name` = 'Fred') which can be used in attribute definition
to count entities for which the condition matches.
1 parent b32ce3c2
......@@ -14,6 +14,7 @@
- [ADDED] `options.rowFormat` added to Query Generator for MySQL dialect using InnoDB engines [#6824] (https://github.com/sequelize/sequelize/issues/6824)
- [FIXED] `Increment` / `Decrement` properly maps to timestamp fields [#6296](https://github.com/sequelize/sequelize/issues/6296)
- [FIXED] Issue with overrriding custom methods with association mixins (all association methods are now exposed) [#6682](https://github.com/sequelize/sequelize/issues/6682)
- [ADDED] Support condition objects in utility functions [#6685](https://github.com/sequelize/sequelize/pull/6685)
## BC breaks:
- `DATEONLY` now returns string in `YYYY-MM-DD` format rather than `Date` type
......
......@@ -861,6 +861,16 @@ instance.updateAttributes({
})
```
Alternatively, a condition object can be used as an argument e.g. to get the count of rows for which the predicate evaluates to true. Works on mysql and sqlite.
```js
sequelize.fn('sum', { age: { $gt: 25 }, name: 'Joe' })
```
An explicit cast is required on postgres.
```js
sequelize.fn('sum', sequelize.cast({ age: { $gt: 25 }, name: 'Joe' }, 'int'))
```
**See:**
* [Model#find](model#find)
......@@ -874,7 +884,7 @@ instance.updateAttributes({
| Name | Type | Description |
| ---- | ---- | ----------- |
| fn | String | The function you want to call |
| args | any | All further arguments will be passed as arguments to the function |
| args | any | All further arguments will be passed as arguments to the function. An argument may be a condition object. |
***
......
......@@ -1698,6 +1698,8 @@ const QueryGenerator = {
} else if (smth instanceof Utils.Cast) {
if (smth.val._isSequelizeMethod) {
result = this.handleSequelizeMethod(smth.val, tableName, factory, options, prepend);
} else if (_.isPlainObject(smth.val)) {
result = this.whereItemsQuery(smth.val);
} else {
result = this.escape(smth.val);
}
......@@ -1707,6 +1709,8 @@ const QueryGenerator = {
result = smth.fn + '(' + smth.args.map(arg => {
if (arg._isSequelizeMethod) {
return this.handleSequelizeMethod(arg, tableName, factory, options, prepend);
} else if (_.isPlainObject(arg)) {
return this.whereItemsQuery(arg);
} else {
return this.escape(arg);
}
......
......@@ -5,7 +5,9 @@
var chai = require('chai')
, expect = chai.expect
, Utils = require(__dirname + '/../../lib/utils')
, Support = require(__dirname + '/support');
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + '/../../lib/data-types')
, Sequelize = require('../../index');
describe(Support.getTestDialectTeaser('Utils'), function() {
describe('removeCommentsFromFunctionString', function() {
......@@ -226,4 +228,82 @@ describe(Support.getTestDialectTeaser('Utils'), function() {
expect(Utils.singularize('status')).to.equal('status');
});
});
describe('Sequelize.fn', function() {
var Airplane;
beforeEach(function() {
Airplane = this.sequelize.define('Airplane', {
wings: DataTypes.INTEGER,
engines: DataTypes.INTEGER
});
return Airplane.sync({ force: true }).then(function () {
return Airplane.bulkCreate([
{
wings: 2,
engines: 0
}, {
wings: 4,
engines: 1
}, {
wings: 2,
engines: 2
}
]);
});
});
if (Support.getTestDialect() !== 'mssql') {
it('accepts condition object (with cast)', function() {
const type = (Support.getTestDialect() === 'mysql') ? 'unsigned': 'int';
return Airplane.findAll({
attributes: [
[this.sequelize.fn('COUNT', '*'), 'count'],
[Sequelize.fn('SUM', Sequelize.cast({
engines: 1
}, type)), 'count-engines'],
[Sequelize.fn('SUM', Sequelize.cast({
$or: {
engines: {
$gt: 1
},
wings: 4
}
}, type)), 'count-engines-wings']
]
}).spread(function (airplane) {
expect(parseInt(airplane.get('count'))).to.equal(3);
expect(parseInt(airplane.get('count-engines'))).to.equal(1);
expect(parseInt(airplane.get('count-engines-wings'))).to.equal(2);
});
});
}
if (Support.getTestDialect() !== 'mssql' && Support.getTestDialect() !== 'postgres') {
it('accepts condition object (auto casting)', function() {
return Airplane.findAll({
attributes: [
[this.sequelize.fn('COUNT', '*'), 'count'],
[Sequelize.fn('SUM', {
engines: 1
}), 'count-engines'],
[Sequelize.fn('SUM', {
$or: {
engines: {
$gt: 1
},
wings: 4
}
}), 'count-engines-wings']
]
}).spread(function (airplane) {
expect(parseInt(airplane.get('count'))).to.equal(3);
expect(parseInt(airplane.get('count-engines'))).to.equal(1);
expect(parseInt(airplane.get('count-engines-wings'))).to.equal(2);
});
});
}
});
});
......@@ -4,7 +4,8 @@ var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + '/../../lib/data-types')
, Utils = require(__dirname + '/../../lib/utils');
, Utils = require(__dirname + '/../../lib/utils')
, Support = require(__dirname + '/../support');
// Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation
......@@ -456,4 +457,23 @@ suite(Support.getTestDialectTeaser('Utils'), function() {
expect(stack[3].getFunctionName()).to.eql('this_here_test');
});
});
suite('Sequelize.cast', function() {
var sql = Support.sequelize;
var generator = sql.queryInterface.QueryGenerator;
var run = generator.handleSequelizeMethod.bind(generator);
var expectsql = Support.expectsql;
test('accepts condition object (auto casting)', function fn() {
expectsql(run(sql.fn('SUM', sql.cast({
$or: {
foo: 'foo',
bar: 'bar'
}
}, 'int'))), {
default: 'SUM(CAST(([foo] = \'foo\' OR [bar] = \'bar\') AS INT))',
mssql: 'SUM(CAST(([foo] = N\'foo\' OR [bar] = N\'bar\') AS INT))'
});
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!