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

Commit 75bd5256 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 cfbd2f70
# Future
- [ADDED] UPSERT support for MSSQL
- [FIXED] Incorrect column name for generateThroughJoin
- [FIXED] Incorrect column name for generateThroughJoin [#6878](https://github.com/sequelize/sequelize/pull/6878)
- [ADDED] Support condition objects in utility functions [#6685](https://github.com/sequelize/sequelize/pull/6685)
# 3.26.0
- [ADDED] Backport of mssql upsert [#6875](https://github.com/sequelize/sequelize/pull/6875)
# 3.25.1
- [FIXED] fix instance.set '1970-01-01' to null field [#6839](https://github.com/sequelize/sequelize/pull/6839)
- [FIXED] Can not execute raw SQL queries for sqlite3 [#6865](https://github.com/sequelize/sequelize/pull/6865)
# 3.25.0
- [FIXED] Set `timestamps` and `paranoid` options from through model on `belongsToMany` association
......
......@@ -860,6 +860,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)
......@@ -873,7 +883,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. |
***
......
......@@ -1923,6 +1923,8 @@ var 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);
}
......@@ -1932,6 +1934,8 @@ var QueryGenerator = {
result = smth.fn + '(' + smth.args.map(function(arg) {
if (arg._isSequelizeMethod) {
return self.handleSequelizeMethod(arg, tableName, factory, options, prepend);
} else if (_.isPlainObject(arg)) {
return self.whereItemsQuery(arg);
} else {
return self.escape(arg);
}
......
'use strict';
exports.default = function(sequelize, DataTypes) {
return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), {
name: DataTypes.STRING
});
};
......@@ -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() {
var 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
......@@ -473,4 +474,23 @@ suite(Support.getTestDialectTeaser('Utils'), function() {
});
});
});
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!