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

Commit ba591e94 by Daniel Durante

Merged master/new smartWhere utility.

2 parents 4914341e f3c7939e
# Sequelize # # Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
<a href="http://flattr.com/thing/1259407/Sequelize" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
## Important Notes ## ## Important Notes ##
...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/ ...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
- Asynchronous library - Asynchronous library
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises
## Documentation and Updates ## ## Documentation and Updates ##
...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - BLOB [#99](https://github.com/sequelize/sequelize/issues/99)
...@@ -167,6 +165,9 @@ $ npm run test-buster-sqlite ...@@ -167,6 +165,9 @@ $ npm run test-buster-sqlite
$ # run the buster specs for postgresql: $ # run the buster specs for postgresql:
$ npm run test-buster-postgres $ npm run test-buster-postgres
$ # alternatively you can pass database credentials with $variables when testing with buster.js
$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password buster-test
``` ```
### 5. That's all ### ### 5. That's all ###
...@@ -235,6 +236,18 @@ for (var key in obj) { ...@@ -235,6 +236,18 @@ for (var key in obj) {
```js ```js
{ {
"globals": {
"jasmine": false,
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -246,10 +259,3 @@ for (var key in obj) { ...@@ -246,10 +259,3 @@ for (var key in obj) {
"es5": true "es5": true
} }
``` ```
# Build status
The automated tests we talk about just so much are running on
[Travis public CI](http://travis-ci.org), here is its status:
[![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize)
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
- [FEATURE] Made explicit migrations possible. [#728](https://github.com/sequelize/sequelize/pull/728). Thanks to freezy - [FEATURE] Made explicit migrations possible. [#728](https://github.com/sequelize/sequelize/pull/728). Thanks to freezy
- [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango - [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango
- [FEATURE] Added support for model instances being referenced [#761](https://github.com/sequelize/sequelize/pull/761) thanks to sdepold - [FEATURE] Added support for model instances being referenced [#761](https://github.com/sequelize/sequelize/pull/761) thanks to sdepold
- [FEATURE] Added support for specifying the path to load a module for a dialect. [#766](https://github.com/sequelize/sequelize/pull/766) thanks to sonnym.
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier - [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
......
...@@ -21,12 +21,26 @@ module.exports = (function() { ...@@ -21,12 +21,26 @@ module.exports = (function() {
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"} where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"}
if (options.where) { if (options.where) {
Utils._.each(options.where, function(value, index) { if (Array.isArray(options.where)) {
delete options.where[index]; smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
options.where[self.__factory.target.tableName+"."+index] = value; smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
}); if (smart.length > 0) {
options.where = smart
Utils._.extend(options.where, where) }
} else {
Utils._.each(options.where, function(value, index) {
delete options.where[index];
smart = Utils.smartWhere(value, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart)
if (smart.length > 0) {
value = smart
}
options.where[self.__factory.target.tableName+"."+index] = value;
});
options.where = Utils._.extend(options.where, where)
}
} else { } else {
options.where = where; options.where = where;
} }
...@@ -51,10 +65,10 @@ module.exports = (function() { ...@@ -51,10 +65,10 @@ module.exports = (function() {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) { , unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
...@@ -81,7 +95,7 @@ module.exports = (function() { ...@@ -81,7 +95,7 @@ module.exports = (function() {
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk)) chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
} }
chainer chainer
.run() .run()
......
...@@ -12,42 +12,51 @@ module.exports = (function() { ...@@ -12,42 +12,51 @@ module.exports = (function() {
where[this.__factory.identifier] = this.instance.id where[this.__factory.identifier] = this.instance.id
options.where = options.where ? Utils._.extend(options.where, where) : where if (options.where) {
return this.__factory.target.findAll(options) smart = Utils.smartWhere([where, options.where], this.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(this.__factory.target, smart)
if (smart.length > 0) {
options.where = smart
}
} else {
options.where = where
}
return this.__factory.target.all(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) { , unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, update , update
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
// clear the old associations // clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) { var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = null associatedObject[self.__factory.identifier] = null
return associatedObject.id return associatedObject.id
}) })
update = {} update = {}
update[self.__factory.identifier] = null update[self.__factory.identifier] = null
chainer.add(this.__factory.target.update(update, { id: obsoleteIds })) chainer.add(this.__factory.target.update(update, { id: obsoleteIds }))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
// set the new associations // set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) { var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id associatedObject[self.__factory.identifier] = self.instance.id
return associatedObject.id return associatedObject.id
}) })
update = {} update = {}
......
...@@ -56,6 +56,12 @@ module.exports = (function() { ...@@ -56,6 +56,12 @@ module.exports = (function() {
params = {where: where} params = {where: where}
} }
smart = Utils.smartWhere([where, params.where || []], self.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.target, smart)
if (smart.length > 0) {
params.where = smart
}
return self.target.find(params) return self.target.find(params)
} }
......
...@@ -4,11 +4,18 @@ var mysql ...@@ -4,11 +4,18 @@ var mysql
, Utils = require("../../utils") , Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) } , without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
try { mysql = require("mysql") } catch (err) {
console.log("You need to install mysql package manually"); }
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
try {
if (config.dialectModulePath) {
mysql = require(config.dialectModulePath)
} else {
mysql = require('mysql')
}
} catch (err) {
console.log('You need to install mysql package manually')
}
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
......
...@@ -3,12 +3,14 @@ var Query = require("./query") ...@@ -3,12 +3,14 @@ var Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
var pgModule = config.dialectModulePath || 'pg'
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.config.port = this.config.port || 5432 this.config.port = this.config.port || 5432
this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0)) this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg') this.pg = this.config.native ? require(pgModule).native : require(pgModule)
// Better support for BigInts // Better support for BigInts
// https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935 // https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935
......
...@@ -289,7 +289,7 @@ module.exports = (function() { ...@@ -289,7 +289,7 @@ module.exports = (function() {
Escape a value (e.g. a string, number or date) Escape a value (e.g. a string, number or date)
*/ */
escape: function(value) { escape: function(value) {
throwMethodUndefined('quoteIdentifier') throwMethodUndefined('escape')
} }
} }
......
var Utils = require("../../utils") var sqlite3
, sqlite3 = require('sqlite3').verbose() , Utils = require("../../utils")
, Query = require("./query") , Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize, config) {
this.sequelize = sequelize this.sequelize = sequelize
if (config.dialectModulePath) {
sqlite3 = require(config.dialectModulePath).verbose()
} else {
sqlite3 = require('sqlite3').verbose()
}
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
......
...@@ -14,6 +14,7 @@ module.exports = (function() { ...@@ -14,6 +14,7 @@ module.exports = (function() {
@param {String} [password=null] The password which is used to authenticate against the database. @param {String} [password=null] The password which is used to authenticate against the database.
@param {Object} [options={}] An object with options. @param {Object} [options={}] An object with options.
@param {String} [options.dialect='mysql'] The dialect of the relational database. @param {String} [options.dialect='mysql'] The dialect of the relational database.
@param {String} [options.dialectModulePath=null] If specified, load the dialect library from this path.
@param {String} [options.host='localhost'] The host of the relational database. @param {String} [options.host='localhost'] The host of the relational database.
@param {Integer} [options.port=3306] The port of the relational database. @param {Integer} [options.port=3306] The port of the relational database.
@param {String} [options.protocol='tcp'] The protocol of the relational database. @param {String} [options.protocol='tcp'] The protocol of the relational database.
...@@ -68,6 +69,7 @@ module.exports = (function() { ...@@ -68,6 +69,7 @@ module.exports = (function() {
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null,
host: 'localhost', host: 'localhost',
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
...@@ -99,6 +101,7 @@ module.exports = (function() { ...@@ -99,6 +101,7 @@ module.exports = (function() {
queue : this.options.queue, queue : this.options.queue,
native : this.options.native, native : this.options.native,
replication: this.options.replication, replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries maxConcurrentQueries: this.options.maxConcurrentQueries
} }
......
...@@ -2,11 +2,12 @@ var util = require("util") ...@@ -2,11 +2,12 @@ var util = require("util")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, SqlString = require("./sql-string") , SqlString = require("./sql-string")
, lodash = require("lodash") , lodash = require("lodash")
, _string = require('underscore.string')
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = lodash var _ = lodash
, _s = require('underscore.string') , _s = _string
_.mixin(_s.exports()) _.mixin(_s.exports())
_.mixin({ _.mixin({
...@@ -109,36 +110,43 @@ var Utils = module.exports = { ...@@ -109,36 +110,43 @@ var Utils = module.exports = {
// All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column // All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column
// This function will hopefully provide more functionality to sequelize in the future. // This function will hopefully provide more functionality to sequelize in the future.
// tl;dr It's a nice way to dissect a collection of where objects and compress them into one object // tl;dr It's a nice way to dissect a collection of where objects and compress them into one object
smartWhere: function(whereArg) { smartWhere: function(whereArg, dialect) {
var self = this var self = this
, _where = {} , _where = {}
, logic , logic
, type , type
whereArg = Array.isArray(whereArg) ? whereArg : [whereArg] (Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) {
whereArg.forEach(function(where){
// If it's an array we're already good... / it's in a format that can't be broken down further // If it's an array we're already good... / it's in a format that can't be broken down further
// e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello'] // e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello']
if (Array.isArray(where)) { if (Array.isArray(where)) {
_where._ = where._ || {queries: [], bindings: []} _where._ = where._ || {queries: [], bindings: []}
_where._.queries[_where._.queries.length] = where[0] _where._.queries[_where._.queries.length] = where[0]
if (where.length > 1) { if (where.length > 1) {
_where._.bindings = _where._.bindings.concat(where.splice(1)) var values = where.splice(1)
if (dialect === "sqlite") {
values.forEach(function(v, i) {
if (typeof v === "boolean") {
values[i] = (v === true ? 1 : 0)
}
})
}
_where._.bindings = _where._.bindings.concat(values)
} }
} }
else if (typeof where === "object") { else if (typeof where === "object") {
// First iteration is trying to compress IN and NOT IN as much as possible... // First iteration is trying to compress IN and NOT IN as much as possible...
// .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?) // .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?)
for (var i in where) { Object.keys(where).forEach(function(i) {
if (Array.isArray(where[i])) { if (Array.isArray(where[i])) {
where[i] = { where[i] = {
in: where[i] in: where[i]
} }
} }
} })
// Build our smart object // Build our smart object
for (var i in where) { Object.keys(where).forEach(function(i) {
type = typeof where[i] type = typeof where[i]
_where[i] = _where[i] || {} _where[i] = _where[i] || {}
...@@ -147,39 +155,47 @@ var Utils = module.exports = { ...@@ -147,39 +155,47 @@ var Utils = module.exports = {
_where[i].in.concat(where[i]); _where[i].in.concat(where[i]);
} }
else if (type === "object") { else if (type === "object") {
for (var ii in where[i]) { Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii) logic = self.getWhereLogic(ii)
switch(logic) { switch(logic) {
case 'IN': case 'IN':
_where[i].in = _where[i].in || [] _where[i].in = _where[i].in || []
_where[i].in = _where[i].in.concat(where[i][ii]); _where[i].in = _where[i].in.concat(where[i][ii]);
break; break
case 'NOT': case 'NOT':
_where[i].not = _where[i].not || [] _where[i].not = _where[i].not || []
_where[i].not = _where[i].not.concat(where[i][ii]); _where[i].not = _where[i].not.concat(where[i][ii]);
break; break
case 'BETWEEN': case 'BETWEEN':
_where[i].between = _where[i].between || [] _where[i].between = _where[i].between || []
_where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]] _where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]]
break; break
case 'NOT BETWEEN': case 'NOT BETWEEN':
_where[i].nbetween = _where[i].nbetween || [] _where[i].nbetween = _where[i].nbetween || []
_where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]] _where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]]
break; break
case 'JOIN':
_where[i].joined = _where[i].joined || []
_where[i].joined[_where[i].joined.length] = where[i][ii]
break
default: default:
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []} _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?' _where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii]) _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii])
} }
} })
} }
else if (type === "string" || type === "number") { else if (type === "string" || type === "number" || type === "boolean") {
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []} _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?' if (type === "boolean") {
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i]) _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special
} else {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i])
}
} }
} })
} }
}) })
...@@ -187,8 +203,9 @@ var Utils = module.exports = { ...@@ -187,8 +203,9 @@ var Utils = module.exports = {
}, },
// Converts {smart where} object(s) into an array that's friendly for Utils.format() // Converts {smart where} object(s) into an array that's friendly for Utils.format()
// NOTE: Must be applied/called from the QueryInterface // NOTE: Must be applied/called from the QueryInterface
compileSmartWhere: function(obj) { compileSmartWhere: function(obj, dialect) {
var whereArgs = [] var self = this
, whereArgs = []
, text = [] , text = []
, columnName , columnName
...@@ -203,34 +220,40 @@ var Utils = module.exports = { ...@@ -203,34 +220,40 @@ var Utils = module.exports = {
whereArgs = whereArgs.concat(obj[column].bindings) whereArgs = whereArgs.concat(obj[column].bindings)
} }
} else { } else {
for (var condition in obj[column]) { Object.keys(obj[column]).forEach(function(condition) {
columnName = this.QueryInterface.quoteIdentifiers(column) columnName = self.QueryInterface.quoteIdentifiers(column)
switch(condition) { switch(condition) {
case 'in': case 'in':
text[text.length] = column + ' IN (' + obj[column][condition].map(function(){ return '?'; }) + ')' text[text.length] = columnName + ' IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition]) whereArgs = whereArgs.concat(obj[column][condition])
break break
case 'not': case 'not':
text[text.length] = column + ' NOT IN (' + obj[column][condition].map(function(){ return '?'; }) + ')' text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition]) whereArgs = whereArgs.concat(obj[column][condition])
break break
case 'between': case 'between':
for (var row in obj[column][condition]) { Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = column + ' BETWEEN ? AND ?' text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]) whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
} })
break break
case 'nbetween': case 'nbetween':
for (var row in obj[column][condition]) { Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = column + ' BETWEEN ? AND ?' text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]) whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
} })
break
case 'joined':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row])
})
break break
default: // lazy default: // lazy
text = text.concat(obj[column].lazy.conditions.map(function(val){ return column + ' ' + val })) text = text.concat(obj[column].lazy.conditions.map(function(val){ return columnName + ' ' + val }))
obj[column].lazy.bindings = obj[column].lazy.bindings.map(function(v) { return SqlString.escape(v, false, null, dialect) })
whereArgs = whereArgs.concat(obj[column].lazy.bindings) whereArgs = whereArgs.concat(obj[column].lazy.bindings)
} }
} })
} }
} }
...@@ -238,46 +261,36 @@ var Utils = module.exports = { ...@@ -238,46 +261,36 @@ var Utils = module.exports = {
}, },
getWhereLogic: function(logic) { getWhereLogic: function(logic) {
switch (logic) { switch (logic) {
case 'join':
return 'JOIN'
case 'gte': case 'gte':
return '>=' return '>='
break
case 'gt': case 'gt':
return '>' return '>'
break
case 'lte': case 'lte':
return '<=' return '<='
break
case 'lt': case 'lt':
return '<' return '<'
break
case 'eq': case 'eq':
return '=' return '='
break
case 'ne': case 'ne':
return '!=' return '!='
break
case 'between': case 'between':
case '..': case '..':
return 'BETWEEN' return 'BETWEEN'
break
case 'nbetween': case 'nbetween':
case 'notbetween': case 'notbetween':
case '!..': case '!..':
return 'NOT BETWEEN' return 'NOT BETWEEN'
break
case 'in': case 'in':
return 'IN' return 'IN'
break
case 'not': case 'not':
return 'NOT IN' return 'NOT IN'
break
case 'like': case 'like':
return 'LIKE' return 'LIKE'
break
case 'nlike': case 'nlike':
case 'notlike': case 'notlike':
return 'NOT LIKE' return 'NOT LIKE'
break
default: default:
return '' return ''
} }
......
...@@ -4,6 +4,7 @@ if (typeof require === 'function') { ...@@ -4,6 +4,7 @@ if (typeof require === 'function') {
, Sequelize = require('../../index') , Sequelize = require('../../index')
, dialect = Helpers.getTestDialect() , dialect = Helpers.getTestDialect()
, _ = require('lodash') , _ = require('lodash')
, moment = require('moment')
} }
buster.spec.expose() buster.spec.expose()
...@@ -24,8 +25,12 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -24,8 +25,12 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
describe('(1:N)', function() { describe('(1:N)', function() {
describe('hasSingle', function() { describe('hasSingle', function() {
before(function(done) { before(function(done) {
this.Article = this.sequelize.define('Article', { 'title': Sequelize.STRING }) this.Article = this.sequelize.define('Article', {
this.Label = this.sequelize.define('Label', { 'text': Sequelize.STRING }) 'title': Sequelize.STRING
})
this.Label = this.sequelize.define('Label', {
'text': Sequelize.STRING
})
this.Article.hasMany(this.Label) this.Article.hasMany(this.Label)
...@@ -67,7 +72,8 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -67,7 +72,8 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
[ article, 'hasLabel', [ label2 ]] [ article, 'hasLabel', [ label2 ]]
]) ])
chainer.runSerially().success(function(_, label1, hasLabel1, hasLabel2) { chainer.runSerially()
.success(function(_, label1, hasLabel1, hasLabel2) {
expect(hasLabel1).toBeTrue() expect(hasLabel1).toBeTrue()
expect(hasLabel2).toBeFalse() expect(hasLabel2).toBeFalse()
done() done()
...@@ -78,8 +84,12 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -78,8 +84,12 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
describe('hasAll', function() { describe('hasAll', function() {
before(function(done) { before(function(done) {
this.Article = this.sequelize.define('Article', { 'title': Sequelize.STRING }) this.Article = this.sequelize.define('Article', {
this.Label = this.sequelize.define('Label', { 'text': Sequelize.STRING }) 'title': Sequelize.STRING
})
this.Label = this.sequelize.define('Label', {
'text': Sequelize.STRING
})
this.Article.hasMany(this.Label) this.Article.hasMany(this.Label)
...@@ -199,6 +209,38 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -199,6 +209,38 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
it('should treat the where object of associations as a first class citizen', function(done) {
var self = this
this.Article = this.sequelize.define('Article', {
'title': Sequelize.STRING
})
this.Label = this.sequelize.define('Label', {
'text': Sequelize.STRING,
'until': Sequelize.DATE
})
this.Article.hasMany(this.Label)
this.sequelize.sync({ force: true }).success(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.Article.create({ title: 'Article' }),
self.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }),
self.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' })
])
chainer.run().success(function(results, article, label1, label2) {
article.setLabels([label1, label2]).success(function() {
article.getLabels({where: ['until > ?', moment('2014-01-02').toDate()]}).success(function(labels) {
expect(labels).toBeArray()
expect(labels.length).toEqual(1)
expect(labels[0].text).toEqual('Epicness')
done()
})
})
})
})
})
it("gets all associated objects when no options are passed", function(done) { it("gets all associated objects when no options are passed", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) { this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) { john.getTasks().success(function (tasks) {
...@@ -309,6 +351,15 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -309,6 +351,15 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
it("only gets objects that fulfill options with a formatted value", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks({where: ['active = ?', true]}).success(function (tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
}) })
it("removes the reference id, which was added in the first place", function() { it("removes the reference id, which was added in the first place", function() {
......
...@@ -19,6 +19,27 @@ describe(Helpers.getTestDialectTeaser("HasOne"), function() { ...@@ -19,6 +19,27 @@ describe(Helpers.getTestDialectTeaser("HasOne"), function() {
}) })
}) })
describe('getAssocation', function() {
it('should be able to handle a where object that\'s a first class citizen.', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task', status: 'inactive' }).success(function(task) {
user.setTaskXYZ(task).success(function() {
user.getTaskXYZ({where: ['status = ?', 'active']}).success(function(task) {
expect(task).toEqual(null)
done()
})
})
})
})
})
})
})
describe('setAssociation', function() { describe('setAssociation', function() {
it('clears the association if null is passed', function(done) { it('clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!