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

Commit 7b07cd00 by Jan Aagaard Meier

Merge latest master

2 parents 2c3cfabb d11da684
...@@ -10,6 +10,8 @@ notifications: ...@@ -10,6 +10,8 @@ notifications:
- sascha@depold.com - sascha@depold.com
hipchat: hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477 - 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
env: env:
- DB=mysql DIALECT=mysql - DB=mysql DIALECT=mysql
...@@ -22,3 +24,8 @@ language: node_js ...@@ -22,3 +24,8 @@ language: node_js
node_js: node_js:
- "0.8" - "0.8"
- "0.10" - "0.10"
branches:
only:
- master
- milestones/2.0.0
...@@ -32,6 +32,14 @@ ...@@ -32,6 +32,14 @@
- [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango - [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango
- [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree - [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree
- [BUG] allowNull: true enums can now be null [#857](https://github.com/sequelize/sequelize/pull/857). thanks to durango - [BUG] allowNull: true enums can now be null [#857](https://github.com/sequelize/sequelize/pull/857). thanks to durango
- [BUG] Fixes Postgres' ability to search within arrays. [#879](https://github.com/sequelize/sequelize/pull/879). thanks to durango
- [BUG] Find and finAll would modify the options objects, now the objects are cloned at the start of the method [#884](https://github.com/sequelize/sequelize/pull/884) thanks to janmeier. Improved in [#899](https://github.com/sequelize/sequelize/pull/899) thanks to hackwaly
- [BUG] Add support for typed arrays in SqlString.escape and SqlString.arrayToList [#891](https://github.com/sequelize/sequelize/pull/891). thanks to LJ1102
- [BUG] Postgres requires empty array to be explicitly cast on update [#890](https://github.com/sequelize/sequelize/pull/890). thanks to robraux
- [BUG] Added tests & bugfixes for DAO-Factory.update and array of values in where clause [#880](https://github.com/sequelize/sequelize/pull/880). thanks to domasx2
- [BUG] sqlite no longer leaks a global `db` variable [#900](https://github.com/sequelize/sequelize/pull/900). thanks to xming
- [BUG] Fix for counts queries with no result [#906](https://github.com/sequelize/sequelize/pull/906). thanks to iamjochem
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango - [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
...@@ -60,8 +68,11 @@ ...@@ -60,8 +68,11 @@
- [FEATURE] Blob support. janmeier - [FEATURE] Blob support. janmeier
- [FEATURE] We can now define our own custom timestamp columns [#856](https://github.com/sequelize/sequelize/pull/856). thanks to durango - [FEATURE] We can now define our own custom timestamp columns [#856](https://github.com/sequelize/sequelize/pull/856). thanks to durango
- [FEATURE] Scopes. [#748](https://github.com/sequelize/sequelize/pull/748). durango - [FEATURE] Scopes. [#748](https://github.com/sequelize/sequelize/pull/748). durango
- [FEATURE] Model#find() / Model#findAll() is now working with strings. [#855](https://github.com/sequelize/sequelize/pull/855). Thanks to whito. - [FEATURE] Model#find() / Model#findAll() is now working with strings. [#855](https://github.com/sequelize/sequelize/pull/855). Thanks to whito.
- [FEATURE] Shortcut method for getting a defined model. [#868](https://github.com/sequelize/sequelize/pull/868). Thanks to jwilm. - [FEATURE] Shortcut method for getting a defined model. [#868](https://github.com/sequelize/sequelize/pull/868). Thanks to jwilm.
- [FEATURE] Added Sequelize.fn() and Sequelize.col() to properly call columns and functions within Sequelize. [#882](https://github.com/sequelize/sequelize/pull/882). thanks to janmeier
- [FEATURE] Sequelize.import supports relative paths. [#901](https://github.com/sequelize/sequelize/pull/901). thanks to accerqueira.
- [FEATURE] Sequelize.import can now handle functions. [#911](https://github.com/sequelize/sequelize/pull/911). Thanks to davidrivera.
- [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
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango - [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
...@@ -26,8 +26,9 @@ module.exports = (function() { ...@@ -26,8 +26,9 @@ module.exports = (function() {
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
) )
} }
this.options.tableName = this.associationAccessor = this.combinedName = combinedTableName
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.associationAccessor = this.options.as || this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language)) var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
this.accessors = { this.accessors = {
......
...@@ -324,8 +324,8 @@ module.exports = (function() { ...@@ -324,8 +324,8 @@ module.exports = (function() {
DAOFactory.prototype.findAll = function(options, queryOptions) { DAOFactory.prototype.findAll = function(options, queryOptions) {
var hasJoin = false var hasJoin = false
var options = Utils._.clone(options)
options = optClone(options)
if (typeof options === 'object') { if (typeof options === 'object') {
if (options.hasOwnProperty('include')) { if (options.hasOwnProperty('include')) {
hasJoin = true hasJoin = true
...@@ -656,7 +656,7 @@ module.exports = (function() { ...@@ -656,7 +656,7 @@ module.exports = (function() {
*/ */
DAOFactory.prototype.update = function(attrValueHash, where, options) { DAOFactory.prototype.update = function(attrValueHash, where, options) {
options = options || {} options = options || {}
options.validate = options.validate || true options.validate = options.validate === undefined ? true : Boolean(options.validate)
if(this.options.timestamps) { if(this.options.timestamps) {
var attr = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored) var attr = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored)
...@@ -664,7 +664,10 @@ module.exports = (function() { ...@@ -664,7 +664,10 @@ module.exports = (function() {
} }
if (options.validate === true) { if (options.validate === true) {
var validate = this.build(attrValueHash).validate() var build = this.build(attrValueHash)
, attrKeys = Object.keys(attrValueHash)
, validate = build.validate({skip: Object.keys(build.dataValues).filter(function(val) { return attrKeys.indexOf(val) !== -1 })})
if (validate !== null && Object.keys(validate).length > 0) { if (validate !== null && Object.keys(validate).length > 0) {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', validate) emitter.emit('error', validate)
...@@ -806,6 +809,13 @@ module.exports = (function() { ...@@ -806,6 +809,13 @@ module.exports = (function() {
return attributes return attributes
} }
var optClone = function (options) {
return Utils._.cloneDeep(options, function (elem) {
// The DAOFactories used for include are pass by ref, so don't clone them. Otherwise return undefined, meaning, 'handle this lodash'
return elem instanceof DAOFactory ? elem : undefined
})
}
Utils._.extend(DAOFactory.prototype, require("./associations/mixin")) Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
return DAOFactory return DAOFactory
......
...@@ -36,7 +36,7 @@ var validateModel = function() { ...@@ -36,7 +36,7 @@ var validateModel = function() {
} }
var validateAttributes = function() { var validateAttributes = function() {
var self = this var self = this
, errors = {} , errors = {}
// for each field and value // for each field and value
......
var Utils = require("../../utils")
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
addSchema: function(opts) { addSchema: function(opts) {
...@@ -268,7 +270,35 @@ module.exports = (function() { ...@@ -268,7 +270,35 @@ module.exports = (function() {
Globally disable foreign key constraints Globally disable foreign key constraints
*/ */
disableForeignKeyConstraintsQuery: function() { disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery') throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
Objects:
* If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by
the values of cols (which is assumed to be an array), quoted and joined with ', ',
unless they are themselves objects
* If direction is set, should be prepended
Currently this function is only used for ordering / grouping columns, but it could
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
*/
quote: function(obj, force) {
if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) {
return this.quote(obj[0], force) + ' ' + obj[1]
} else if (obj instanceof Utils.fn || obj instanceof Utils.col) {
return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) {
return obj.raw
} else {
throw new Error('Unknown structure passed to order / group: ' + JSON.stringify(obj))
}
}, },
/* /*
......
...@@ -228,11 +228,12 @@ module.exports = (function() { ...@@ -228,11 +228,12 @@ module.exports = (function() {
} }
if (options.group) { if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : this.quoteIdentifiers(options.group) options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY " + options.group query += " GROUP BY " + options.group
} }
if (options.order) { if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY " + options.order query += " ORDER BY " + options.order
} }
...@@ -612,5 +613,5 @@ module.exports = (function() { ...@@ -612,5 +613,5 @@ module.exports = (function() {
} }
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator) return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
})() })()
...@@ -19,9 +19,9 @@ module.exports = (function() { ...@@ -19,9 +19,9 @@ module.exports = (function() {
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.pool.maxConnections this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000 this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
...@@ -142,7 +142,9 @@ module.exports = (function() { ...@@ -142,7 +142,9 @@ module.exports = (function() {
} }
if (this.client) { if (this.client) {
this.client.end.bind(this.client) // Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client))
} }
this.isConnecting = false this.isConnecting = false
......
...@@ -300,14 +300,12 @@ module.exports = (function() { ...@@ -300,14 +300,12 @@ module.exports = (function() {
} }
if(options.group) { if(options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(', ') : this.quoteIdentifiers(options.group) options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY <%= group %>" query += " GROUP BY <%= group %>"
} }
if(options.order) { if(options.order) {
options.order = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) { options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
return this.quoteIdentifiers(g1) + g2
}.bind(this))
query += " ORDER BY <%= order %>" query += " ORDER BY <%= order %>"
} }
...@@ -382,7 +380,7 @@ module.exports = (function() { ...@@ -382,7 +380,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
updateQuery: function(tableName, attrValueHash, where, options) { updateQuery: function(tableName, attrValueHash, where, options, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
...@@ -390,7 +388,7 @@ module.exports = (function() { ...@@ -390,7 +388,7 @@ module.exports = (function() {
for (var key in attrValueHash) { for (var key in attrValueHash) {
var value = attrValueHash[key] var value = attrValueHash[key]
values.push(this.quoteIdentifier(key) + "=" + this.escape(value)) values.push(this.quoteIdentifier(key) + "=" + this.escape(value, (!!attributes && !!attributes[key] ? attributes[key] : undefined)))
} }
var replacements = { var replacements = {
...@@ -402,7 +400,7 @@ module.exports = (function() { ...@@ -402,7 +400,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options, factory) {
options = options || {} options = options || {}
if (options.truncate === true) { if (options.truncate === true) {
...@@ -415,6 +413,10 @@ module.exports = (function() { ...@@ -415,6 +413,10 @@ module.exports = (function() {
primaryKeys[tableName] = primaryKeys[tableName] || []; primaryKeys[tableName] = primaryKeys[tableName] || [];
if (!!factory && primaryKeys[tableName].length < 1) {
primaryKeys[tableName] = Object.keys(factory.primaryKeys)
}
var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)" var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)"
var pks; var pks;
...@@ -526,7 +528,7 @@ module.exports = (function() { ...@@ -526,7 +528,7 @@ module.exports = (function() {
if (Utils.isHash(smth)) { if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth) smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth) result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') { } else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [] var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) { if (primaryKeys.length > 0) {
...@@ -549,7 +551,7 @@ module.exports = (function() { ...@@ -549,7 +551,7 @@ module.exports = (function() {
return result return result
}, },
hashToWhereConditions: function(hash) { hashToWhereConditions: function(hash, factory) {
var result = [] var result = []
for (var key in hash) { for (var key in hash) {
...@@ -561,9 +563,23 @@ module.exports = (function() { ...@@ -561,9 +563,23 @@ module.exports = (function() {
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length === 0) { value = [null] } if (value.length === 0) { value = [null] }
_value = "(" + value.map(this.escape).join(',') + ")" var col = null, coltype = null
// Special conditions for searching within an array column type
result.push([_key, _value].join(" IN ")) var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey]
coltype = col.type
if(coltype && !(typeof coltype == 'string')) {
coltype = coltype.toString();
}
}
if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
_value = 'ARRAY[' + value.map(this.escape).join(',') + ']::' + (!!col.type ? col.type : col.toString())
result.push([_key, _value].join(" && "))
} else {
_value = "(" + value.map(this.escape).join(',') + ")"
result.push([_key, _value].join(" IN "))
}
} }
else if ((value) && (typeof value === "object")) { else if ((value) && (typeof value === "object")) {
if (!!value.join) { if (!!value.join) {
...@@ -868,5 +884,5 @@ module.exports = (function() { ...@@ -868,5 +884,5 @@ module.exports = (function() {
return returning; return returning;
} }
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator) return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
})() })()
...@@ -20,34 +20,34 @@ module.exports = (function() { ...@@ -20,34 +20,34 @@ module.exports = (function() {
Query.prototype.run = function(sql) { Query.prototype.run = function(sql) {
this.sql = sql this.sql = sql
var self = this var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) { query.on('row', function(row) {
rows.push(row) rows.push(row)
}) })
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
this.emit('error', err, this.callee) self.emit('error', err, self.callee)
}.bind(this)) })
query.on('end', function() { query.on('end', function() {
this.emit('sql', this.sql) self.emit('sql', self.sql)
if (receivedError) { if (receivedError) {
return return
} }
onSuccess.call(this, rows, sql) onSuccess.call(self, rows, sql)
}.bind(this)) })
return this return this
} }
......
...@@ -18,6 +18,7 @@ module.exports = (function() { ...@@ -18,6 +18,7 @@ module.exports = (function() {
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function() {
var emitter = new (require('events').EventEmitter)() var emitter = new (require('events').EventEmitter)()
, self = this , self = this
, db
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) { this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) { if (err) {
......
...@@ -3,7 +3,7 @@ var Utils = require("../../utils") ...@@ -3,7 +3,7 @@ var Utils = require("../../utils")
, SqlString = require("../../sql-string") , SqlString = require("../../sql-string")
var MySqlQueryGenerator = Utils._.extend( var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../query-generator")), Utils._.clone(require("../abstract/query-generator")),
Utils._.clone(require("../mysql/query-generator")) Utils._.clone(require("../mysql/query-generator"))
) )
...@@ -211,11 +211,12 @@ module.exports = (function() { ...@@ -211,11 +211,12 @@ module.exports = (function() {
} }
if (options.group) { if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : qa(options.group) options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY " + options.group query += " GROUP BY " + options.group
} }
if (options.order) { if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY " + options.order query += " ORDER BY " + options.order
} }
......
...@@ -417,7 +417,7 @@ module.exports = (function() { ...@@ -417,7 +417,7 @@ module.exports = (function() {
QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) { QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) {
var self = this var self = this
, restrict = false , restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options) , sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, dao.daoFactory.rawAttributes)
// Check for a restrict field // Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) { if (!!dao.daoFactory && !!dao.daoFactory.associations) {
...@@ -481,7 +481,7 @@ module.exports = (function() { ...@@ -481,7 +481,7 @@ module.exports = (function() {
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var self = this var self = this
, restrict = false , restrict = false
, sql = self.QueryGenerator.deleteQuery(tableName, identifier) , sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, dao.daoFactory)
// Check for a restrict field // Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) { if (!!dao.daoFactory && !!dao.daoFactory.associations) {
...@@ -581,10 +581,10 @@ module.exports = (function() { ...@@ -581,10 +581,10 @@ module.exports = (function() {
qry qry
.success(function(data) { .success(function(data) {
var result = data[attributeSelector] var result = data ? data[attributeSelector] : null
if (options && options.parseInt) { if (options && options.parseInt) {
result = parseInt(result) result = parseInt(result, 10)
} }
if (options && options.parseFloat) { if (options && options.parseFloat) {
......
var url = require("url") var url = require("url")
, Path = require("path")
, Utils = require("./utils") , Utils = require("./utils")
, DAOFactory = require("./dao-factory") , DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
...@@ -215,8 +216,18 @@ module.exports = (function() { ...@@ -215,8 +216,18 @@ module.exports = (function() {
} }
Sequelize.prototype.import = function(path) { Sequelize.prototype.import = function(path) {
// is it a relative path?
if (url.parse(path).pathname.indexOf('/') !== 0) {
// make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName()
, callerMatch = callerFilename.match(/(.+\/).+?$/)
, callerPath = callerMatch[1]
path = Path.resolve(callerPath, path)
}
if (!this.importCache[path]) { if (!this.importCache[path]) {
var defineCall = require(path) var defineCall = (arguments.length > 1 ? arguments[1] : require(path))
this.importCache[path] = defineCall(this, DataTypes) this.importCache[path] = defineCall(this, DataTypes)
} }
...@@ -318,5 +329,13 @@ module.exports = (function() { ...@@ -318,5 +329,13 @@ module.exports = (function() {
}).run() }).run()
} }
Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
}
Sequelize.prototype.col = function (col) {
return new Utils.col(col)
}
return Sequelize return Sequelize
})() })()
var moment = require("moment") var moment = require("moment")
, isArrayBufferView
, SqlString = exports; , SqlString = exports;
if (typeof ArrayBufferView === 'function') {
isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView) }
} else {
var arrayBufferViews = [
Int8Array, Uint8Array, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
]
isArrayBufferView = function(object) {
for (var i=0; i<8; i++) {
if (object instanceof arrayBufferViews[i]) {
return true
}
}
return false
};
}
SqlString.escapeId = function (val, forbidQualified) { SqlString.escapeId = function (val, forbidQualified) {
if (forbidQualified) { if (forbidQualified) {
return '`' + val.replace(/`/g, '``') + '`'; return '`' + val.replace(/`/g, '``') + '`'
} }
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'
}; }
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") { if (arguments.length === 1 && typeof arguments[0] === "object") {
...@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
} }
if (val instanceof Date) { if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z", dialect); val = SqlString.dateToString(val, timeZone || "Z", dialect)
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val, dialect); return SqlString.bufferToString(val, dialect)
} }
if (Array.isArray(val) || isArrayBufferView(val)) {
if (Array.isArray(val)) { return SqlString.arrayToList(val, timeZone, dialect, field)
return SqlString.arrayToList(val, timeZone, dialect, field);
} }
if (typeof val === 'object') { if (typeof val === 'object') {
if (stringifyObjects) { if (stringifyObjects) {
val = val.toString(); val = val.toString()
} else { } else {
return SqlString.objectToValues(val, timeZone); return SqlString.objectToValues(val, timeZone)
} }
} }
if (dialect === 'postgres' || dialect === 'sqlite') { if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
// http://stackoverflow.com/q/603572/130598 // http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''"); val = val.replace(/'/g, "''")
} else { } else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) { switch(s) {
...@@ -71,115 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -71,115 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
case "\x1a": return "\\Z"; case "\x1a": return "\\Z";
default: return "\\"+s; default: return "\\"+s;
} }
}); })
} }
return "'"+val+"'"; return "'"+val+"'"
}; }
SqlString.arrayToList = function(array, timeZone, dialect, field) { SqlString.arrayToList = function(array, timeZone, dialect, field) {
if (dialect === 'postgres') { if (dialect === 'postgres') {
var ret = 'ARRAY[' + array.map(function(v) { if (array.map) {
return SqlString.escape(v, true, timeZone, dialect, field); var valstr = array.map(function(v) {
}).join(',') + ']'; return SqlString.escape(v, true, timeZone, dialect, field)
}).join(',')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ','
}
valstr = valstr.slice(0,-1)
}
var ret = 'ARRAY[' + valstr + ']'
if (!!field && !!field.type) { if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, ''); ret += '::' + field.type.replace(/\(\d+\)/g, '')
} }
return ret; return ret
} else { } else {
return array.map(function(v) { if (array.map) {
if (Array.isArray(v)) return array.map(function(v) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'; if (Array.isArray(v)) {
return SqlString.escape(v, true, timeZone, dialect); return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
}).join(', '); }
return SqlString.escape(v, true, timeZone, dialect)
}).join(', ')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', '
}
return valstr.slice(0, -2)
}
} }
}; }
SqlString.format = function(sql, values, timeZone, dialect) { SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values); values = [].concat(values);
return sql.replace(/\?/g, function(match) { return sql.replace(/\?/g, function(match) {
if (!values.length) { if (!values.length) {
return match; return match
} }
return SqlString.escape(values.shift(), false, timeZone, dialect); return SqlString.escape(values.shift(), false, timeZone, dialect)
}); })
}; }
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) { SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) { return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) { if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect); return SqlString.escape(values[key], false, timeZone, dialect)
} } else {
else { throw new Error('Named parameter "' + value + '" has no value in the given object.')
throw new Error('Named parameter "' + value + '" has no value in the given object.');
} }
}); })
}; }
SqlString.dateToString = function(date, timeZone, dialect) { SqlString.dateToString = function(date, timeZone, dialect) {
var dt = new Date(date); var dt = new Date(date)
// TODO: Ideally all dialects would work a bit more like this // TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") { if (dialect === "postgres") {
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z"); return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z")
} }
if (timeZone !== 'local') { if (timeZone !== 'local') {
var tz = convertTimezone(timeZone); var tz = convertTimezone(timeZone)
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000)); dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000))
if (tz !== false) { if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000)); dt.setTime(dt.getTime() + (tz * 60000))
} }
} }
return moment(dt).format("YYYY-MM-DD HH:mm:ss"); return moment(dt).format("YYYY-MM-DD HH:mm:ss")
}; }
SqlString.bufferToString = function(buffer, dialect) { SqlString.bufferToString = function(buffer, dialect) {
var hex = ''; var hex = ''
try { try {
hex = buffer.toString('hex'); hex = buffer.toString('hex')
} catch (err) { } catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error // node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) { for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i]; var byte = buffer[i]
hex += zeroPad(byte.toString(16)); hex += zeroPad(byte.toString(16))
} }
} }
if (dialect === 'postgres') { if (dialect === 'postgres') {
// bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex+ "'"; return "E'\\\\x" + hex+ "'"
} }
return "X'" + hex+ "'"; return "X'" + hex+ "'"
}; }
SqlString.objectToValues = function(object, timeZone) { SqlString.objectToValues = function(object, timeZone) {
var values = []; var values = []
for (var key in object) { for (var key in object) {
var value = object[key]; var value = object[key]
if(typeof value === 'function') { if(typeof value === 'function') {
continue; continue;
} }
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone)); values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone))
} }
return values.join(', '); return values.join(', ')
}; }
function zeroPad(number) { function zeroPad(number) {
return (number < 10) ? '0' + number : number; return (number < 10) ? '0' + number : number
} }
function convertTimezone(tz) { function convertTimezone(tz) {
if (tz == "Z") return 0; if (tz == "Z") {
return 0
}
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/)
if (m) { if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60
} }
return false; return false
} }
...@@ -55,7 +55,8 @@ var Utils = module.exports = { ...@@ -55,7 +55,8 @@ var Utils = module.exports = {
}, },
format: function(arr, dialect) { format: function(arr, dialect) {
var timeZone = null; var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect) // Make a clone of the array beacuse format modifies the passed args
return SqlString.format(arr[0], arr.slice(1), timeZone, dialect)
}, },
formatNamedParameters: function(sql, parameters, dialect) { formatNamedParameters: function(sql, parameters, dialect) {
var timeZone = null; var timeZone = null;
...@@ -451,6 +452,18 @@ var Utils = module.exports = { ...@@ -451,6 +452,18 @@ var Utils = module.exports = {
return subClass; return subClass;
}, },
stack: function() {
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack){ return stack; };
var err = new Error();
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
},
now: function(dialect) { now: function(dialect) {
var now = new Date() var now = new Date()
if(dialect != "postgres") now.setMilliseconds(0) if(dialect != "postgres") now.setMilliseconds(0)
...@@ -468,9 +481,33 @@ var Utils = module.exports = { ...@@ -468,9 +481,33 @@ var Utils = module.exports = {
removeTicks: function(s, tickChar) { removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "") return s.replace(new RegExp(tickChar, 'g'), "")
},
/*
* Utility functions for representing SQL functions, and columns that should be escaped.
* Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
*/
fn: function (fn, args) {
this.fn = fn
this.args = args
},
col: function (col) {
this.col = col
} }
} }
Utils.fn.prototype.toString = function(queryGenerator) {
return this.fn + '(' + this.args.map(function (arg) {
if (arg instanceof Utils.fn || arg instanceof Utils.col) {
return arg.toString(queryGenerator)
} else {
return queryGenerator.escape(arg)
}
}).join(', ') + ')'
}
Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col)
}
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter") Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.QueryChainer = require(__dirname + "/query-chainer") Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
...@@ -36,11 +36,11 @@ ...@@ -36,11 +36,11 @@
"url": "https://github.com/sequelize/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~1.3.1", "lodash": "~2.0.0",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "~1.5.0", "validator": "~1.5.0",
"moment": "~2.1.0", "moment": "~2.2.1",
"commander": "~2.0.0", "commander": "~2.0.0",
"dottie": "0.0.8-0", "dottie": "0.0.8-0",
"toposort-class": "~0.2.0", "toposort-class": "~0.2.0",
...@@ -51,11 +51,11 @@ ...@@ -51,11 +51,11 @@
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8", "mysql": "~2.0.0-alpha8",
"pg": "~2.3.1", "pg": "~2.6.0",
"watchr": "~2.4.3", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"chai": "~1.7.2", "chai": "~1.7.2",
"mocha": "~1.12.0", "mocha": "~1.13.0",
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3" "sinon": "~1.7.3"
}, },
...@@ -77,4 +77,4 @@ ...@@ -77,4 +77,4 @@
"node": ">=0.4.6" "node": ">=0.4.6"
}, },
"license": "MIT" "license": "MIT"
} }
\ No newline at end of file
/* jshint camelcase: false */ /* jshint camelcase: false, expr: true */
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
...@@ -7,6 +7,19 @@ var chai = require('chai') ...@@ -7,6 +7,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("BelongsTo"), function() { describe(Support.getTestDialectTeaser("BelongsTo"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.belongsTo(User)
Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
})
})
describe('setAssociation', function() { describe('setAssociation', function() {
it('can set the association with declared primary keys...', function(done) { it('can set the association with declared primary keys...', function(done) {
var User = this.sequelize.define('UserXYZ', { user_id: {type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING }) var User = this.sequelize.define('UserXYZ', { user_id: {type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING })
......
/* jshint camelcase: false */ /* jshint camelcase: false, expr: true */
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
...@@ -11,6 +11,19 @@ var chai = require('chai') ...@@ -11,6 +11,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("HasMany"), function() { describe(Support.getTestDialectTeaser("HasMany"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.hasMany(User)
Group.hasMany(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasMany(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['GroupsUsers', 'primaryUsers', 'secondaryUsers'])
})
})
describe('(1:N)', function() { describe('(1:N)', function() {
describe('hasSingle', function() { describe('hasSingle', function() {
beforeEach(function(done) { beforeEach(function(done) {
......
/* jshint camelcase: false */ /* jshint camelcase: false, expr: true */
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
...@@ -7,6 +7,19 @@ var chai = require('chai') ...@@ -7,6 +7,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("HasOne"), function() { describe(Support.getTestDialectTeaser("HasOne"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.hasOne(User)
Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasOne(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
})
})
describe('getAssocation', function() { describe('getAssocation', function() {
it('should be able to handle a where object that\'s a first class citizen.', function(done) { 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 }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
...@@ -333,7 +333,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -333,7 +333,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
describe('create', function() { describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) { it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") { if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('') expect('').to.equal('')
return done() return done()
...@@ -352,6 +352,30 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -352,6 +352,30 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [1,2,3,4], mystr: ["One", "Two", "Three", "Four"]}).on('success', function(user){
user.myvals = []
user.mystr = []
user.save().on('sql', function(sql) {
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) { it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', { var User = this.sequelize.define('UserWithUniqueUsername', {
...@@ -1064,6 +1088,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1064,6 +1088,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('should be able to find rows where attribute is in a list of values', function (done) {
this.User.findAll({
where: {
username: ['boo', 'boo2']
}
}).success(function(users){
expect(users).to.have.length(2);
done()
});
})
it('should not break when trying to find rows using an array of primary keys', function (done) {
this.User.findAll({
where: {
id: [1, 2, 3]
}
}).success(function(users){
done();
});
})
it('should be able to find a row using like', function(done) { it('should be able to find a row using like', function(done) {
this.User.findAll({ this.User.findAll({
where: { where: {
...@@ -1426,14 +1471,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1426,14 +1471,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var self = this var self = this
this.User.create({username: 'barfooz'}).success(function(user) { this.User.create({username: 'barfooz'}).success(function(user) {
self.UserPrimary = self.sequelize.define('UserPrimary', { self.UserPrimary = self.sequelize.define('UserPrimary', {
specialKey: { specialkey: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.UserPrimary.sync({force: true}).success(function() { self.UserPrimary.sync({force: true}).success(function() {
self.UserPrimary.create({specialKey: 'a string'}).success(function() { self.UserPrimary.create({specialkey: 'a string'}).success(function() {
self.user = user self.user = user
done() done()
}) })
...@@ -1441,9 +1486,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1441,9 +1486,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('does not modify the passed arguments', function (done) {
var options = { where: ['specialkey = ?', 'awesome']}
this.UserPrimary.find(options).success(function(user) {
expect(options).to.deep.equal({ where: ['specialkey = ?', 'awesome']})
done()
})
})
it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) { it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) {
this.UserPrimary.find('a string').success(function(user) { this.UserPrimary.find('a string').success(function(user) {
expect(user.specialKey).to.equal('a string') expect(user.specialkey).to.equal('a string')
done() done()
}) })
}) })
...@@ -1800,17 +1854,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1800,17 +1854,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) { it('eager loads with non-id primary keys', function(done) {
var self = this var self = this
self.User = self.sequelize.define('UserPKeagerbelong', { self.User = self.sequelize.define('UserPKeagerbelong', {
username: { username: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.Group = self.sequelize.define('GroupPKeagerbelong', { self.Group = self.sequelize.define('GroupPKeagerbelong', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.User.belongsTo(self.Group) self.User.belongsTo(self.Group)
...@@ -1833,6 +1887,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1833,6 +1887,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.belongsTo(this.Task, { as: 'ToDo' })
this.Worker.belongsTo(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
}) })
describe('hasOne', function() { describe('hasOne', function() {
...@@ -1869,17 +1941,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1869,17 +1941,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) { it('eager loads with non-id primary keys', function(done) {
var self = this var self = this
self.User = self.sequelize.define('UserPKeagerone', { self.User = self.sequelize.define('UserPKeagerone', {
username: { username: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.Group = self.sequelize.define('GroupPKeagerone', { self.Group = self.sequelize.define('GroupPKeagerone', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.Group.hasOne(self.User) self.Group.hasOne(self.User)
...@@ -1954,6 +2026,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1954,6 +2026,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
}) })
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasOne(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
}) })
}) })
...@@ -1991,17 +2080,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1991,17 +2080,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) { it('eager loads with non-id primary keys', function(done) {
var self = this var self = this
self.User = self.sequelize.define('UserPKeagerone', { self.User = self.sequelize.define('UserPKeagerone', {
username: { username: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.Group = self.sequelize.define('GroupPKeagerone', { self.Group = self.sequelize.define('GroupPKeagerone', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
} }
}) })
self.Group.hasMany(self.User) self.Group.hasMany(self.User)
self.User.hasMany(self.Group) self.User.hasMany(self.Group)
...@@ -2023,7 +2112,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2023,7 +2112,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(someUser.groupPKeagerones[0].name).to.equal('people') expect(someUser.groupPKeagerones[0].name).to.equal('people')
done() done()
}) })
}) })
}) })
}) })
}) })
...@@ -2080,6 +2169,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2080,6 +2169,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done() done()
}) })
}) })
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'DoTos' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDos' },
{ model: self.Task, as: 'DoTos' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
}) })
}) })
}) })
...@@ -2458,6 +2564,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2458,6 +2564,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('does not modify the passed arguments', function (done) {
var options = { where: ['username = ?', 'awesome']}
this.User.findAll(options).success(function(user) {
expect(options).to.deep.equal({ where: ['username = ?', 'awesome']})
done()
})
})
it("finds all users matching the passed conditions", function(done) { it("finds all users matching the passed conditions", function(done) {
this.User.findAll({where: "id != " + this.users[1].id}).success(function(users) { this.User.findAll({where: "id != " + this.users[1].id}).success(function(users) {
expect(users.length).to.equal(1) expect(users.length).to.equal(1)
...@@ -2492,7 +2607,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2492,7 +2607,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it("sorts the results via a date column", function(done) { it("sorts the results via a date column", function(done) {
var self = this var self = this
self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function(){ self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function(){
self.User.findAll({ order: 'theDate DESC' }).success(function(users) { self.User.findAll({ order: [['theDate', 'DESC']] }).success(function(users) {
expect(users[0].id).to.be.above(users[2].id) expect(users[0].id).to.be.above(users[2].id)
done() done()
}) })
...@@ -2689,6 +2804,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2689,6 +2804,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('does not modify the passed arguments', function (done) {
var options = { where: ['username = ?', 'user1']}
this.User.count(options).success(function(count) {
expect(options).to.deep.equal({ where: ['username = ?', 'user1']})
done()
})
})
it('allows sql logging', function(done) { it('allows sql logging', function(done) {
this.User.count().on('sql', function(sql) { this.User.count().on('sql', function(sql) {
expect(sql).to.exist expect(sql).to.exist
......
...@@ -1000,6 +1000,34 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -1000,6 +1000,34 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
}) })
it('destroys a record with a primary key of something other than id', function(done) {
var UserDestroy = this.sequelize.define('UserDestroy', {
newId: {
type: DataTypes.STRING,
primaryKey: true
},
email: DataTypes.STRING
})
UserDestroy.sync().success(function() {
UserDestroy.create({newId: '123ABC', email: 'hello'}).success(function() {
UserDestroy.find({where: {email: 'hello'}}).success(function(user) {
user.destroy().on('sql', function(sql) {
if (dialect === "postgres" || dialect === "postgres-native") {
expect(sql).to.equal('DELETE FROM "UserDestroys" WHERE "newId" IN (SELECT "newId" FROM "UserDestroys" WHERE "newId"=\'123ABC\' LIMIT 1)')
}
else if (dialect === "mysql") {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1")
} else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'")
}
done()
})
})
})
})
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) { it("sets deletedAt property to a specific date when deleting an instance", function(done) {
var self = this var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() { this.ParanoidUser.create({ username: 'fnord' }).success(function() {
......
...@@ -254,6 +254,35 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -254,6 +254,35 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
} }
describe('#update', function() { describe('#update', function() {
it('should allow us to update specific columns without tripping the validations', function(done) {
var User = this.sequelize.define('model', {
username: Sequelize.STRING,
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: {
msg: 'You must enter a valid email address'
}
}
}
})
User.sync({ force: true }).success(function() {
User.create({ username: 'bob', email: 'hello@world.com' }).success(function(user) {
User
.update({ username: 'toni' }, { id: user.id })
.error(function(err) { console.log(err) })
.success(function() {
User.find(1).success(function(user) {
expect(user.username).to.equal('toni')
done()
})
})
})
})
})
it('should be able to emit an error upon updating when a validation has failed from an instance', function(done) { it('should be able to emit an error upon updating when a validation has failed from an instance', function(done) {
var Model = this.sequelize.define('model', { var Model = this.sequelize.define('model', {
name: { name: {
......
...@@ -163,20 +163,77 @@ if (dialect.match(/^mysql/)) { ...@@ -163,20 +163,77 @@ if (dialect.match(/^mysql/)) {
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;", expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator context: QueryGenerator
}, { }, {
arguments: ['myTable', {order: ["id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `myTable`.`id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id` DESC;",
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'}, 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(id)) DESC;",
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55') ASC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}], arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;", expectation: "SELECT * FROM `myTable` GROUP BY name;",
context: QueryGenerator context: QueryGenerator
}, { }, {
arguments: ['myTable', {group: ["name"]}], arguments: ['myTable', { group: ["name"] }],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;", expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator context: QueryGenerator
}, { }, {
arguments: ['myTable', {group: ["name", "title"]}], title: 'functions work for group by',
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;", arguments: ['myTable', function (sequelize) {
context: QueryGenerator return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;",
context: QueryGenerator,
needsSequelize: true
}, { }, {
arguments: ['myTable', {group: "name", order: "id DESC"}], arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name` ORDER BY id DESC;", expectation: "SELECT * FROM `myTable` GROUP BY name ORDER BY id DESC;",
context: QueryGenerator context: QueryGenerator
}, { }, {
arguments: ['myTable', {limit: 10}], arguments: ['myTable', {limit: 10}],
...@@ -423,9 +480,12 @@ if (dialect.match(/^mysql/)) { ...@@ -423,9 +480,12 @@ if (dialect.match(/^mysql/)) {
describe(suiteTitle, function() { describe(suiteTitle, function() {
tests.forEach(function(test) { tests.forEach(function(test) {
var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) { it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation) expect(conditions).to.deep.equal(test.expectation)
......
...@@ -26,6 +26,28 @@ if (dialect.match(/^postgres/)) { ...@@ -26,6 +26,28 @@ if (dialect.match(/^postgres/)) {
done() done()
}) })
it('should be able to search within an array', function(done) {
this.User.all({where: {email: ['hello', 'world']}}).on('sql', function(sql) {
expect(sql).to.equal('SELECT * FROM "Users" WHERE "Users"."email" && ARRAY[\'hello\',\'world\']::TEXT[];')
done()
})
})
it('should be able to find a record while searching in an array', function(done) {
var self = this
this.User.bulkCreate([
{username: 'bob', email: ['myemail@email.com']},
{username: 'tony', email: ['wrongemail@email.com']}
]).success(function() {
self.User.all({where: {email: ['myemail@email.com']}}).success(function(user) {
expect(user).to.be.instanceof(Array)
expect(user).to.have.length(1)
expect(user[0].username).to.equal('bob')
done()
})
})
})
it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) { it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) { this.sequelize.queryInterface.describeTable('Users').success(function(table) {
expect(table.document.type).to.equal('HSTORE') expect(table.document.type).to.equal('HSTORE')
......
...@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) { ...@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)}, email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
numbers: {type: DataTypes.ARRAY(DataTypes.FLOAT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'} document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
}) })
this.User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
...@@ -246,13 +247,73 @@ if (dialect.match(/^postgres/)) { ...@@ -246,13 +247,73 @@ if (dialect.match(/^postgres/)) {
expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';" expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';"
}, { }, {
arguments: ['myTable', {order: "id DESC"}], arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM \"myTable\" ORDER BY \"id\" DESC;" expectation: "SELECT * FROM \"myTable\" ORDER BY id DESC;"
}, { }, {
arguments: ['myTable', {order: ["id"]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "id";',
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";',
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "id" DESC;',
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'},'DESC']]}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2(id)) DESC;',
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2("id")) DESC;',
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1("myTable"."id") DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55.000 +00:00\') ASC;',
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}], arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";" expectation: "SELECT * FROM \"myTable\" GROUP BY name;"
}, { }, {
arguments: ['myTable', {group: ["name"]}], arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";" expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";"
}, {
title: 'functions work for group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM \"myTable\" GROUP BY YEAR(\"createdAt\");",
needsSequelize: true
},{
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM \"myTable\" GROUP BY YEAR(\"createdAt\"), \"title\";",
context: QueryGenerator,
needsSequelize: true
}, { }, {
arguments: ['myTable', {group: ["name","title"]}], arguments: ['myTable', {group: ["name","title"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\", \"title\";" expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\", \"title\";"
...@@ -361,6 +422,9 @@ if (dialect.match(/^postgres/)) { ...@@ -361,6 +422,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {data: new Buffer('Sequelize') }], arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]) RETURNING *;"
}, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
}, { }, {
...@@ -403,6 +467,10 @@ if (dialect.match(/^postgres/)) { ...@@ -403,6 +467,10 @@ if (dialect.match(/^postgres/)) {
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;", expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
...@@ -536,6 +604,9 @@ if (dialect.match(/^postgres/)) { ...@@ -536,6 +604,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\"='foo' RETURNING *"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, { }, {
...@@ -575,6 +646,10 @@ if (dialect.match(/^postgres/)) { ...@@ -575,6 +646,10 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
...@@ -792,6 +867,9 @@ if (dialect.match(/^postgres/)) { ...@@ -792,6 +867,9 @@ if (dialect.match(/^postgres/)) {
it(title, function(done) { it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation) expect(conditions).to.deep.equal(test.expectation)
......
...@@ -375,8 +375,20 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -375,8 +375,20 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
describe('import', function() { describe('import', function() {
it("imports a dao definition from a file", function(done) { it("imports a dao definition from a file absolute path", function(done) {
var Project = this.sequelize.import(__dirname + "/assets/project") var Project = this.sequelize.import(__dirname + "/assets/project")
expect(Project).to.exist
done()
})
it("imports a dao definition from a function", function(done) {
var Project = this.sequelize.import('Project', function(sequelize, DataTypes) {
return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), {
name: DataTypes.STRING
})
})
expect(Project).to.exist expect(Project).to.exist
done() done()
}) })
......
...@@ -104,6 +104,156 @@ if (dialect === 'sqlite') { ...@@ -104,6 +104,156 @@ if (dialect === 'sqlite') {
} }
], ],
selectQuery: [
{
arguments: ['myTable'],
expectation: "SELECT * FROM `myTable`;",
context: QueryGenerator
}, {
arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT `id`, `name` FROM `myTable`;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo\'\';DROP TABLE myTable;';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;',
context: QueryGenerator
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM `myTable` WHERE foo='bar';",
context: QueryGenerator
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `myTable`.`id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id` DESC;",
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'}, 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(id)) DESC;",
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55') ASC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY name;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
title: 'functions work for group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;",
context: QueryGenerator,
needsSequelize: true
}, {
arguments: ['myTable', {group: ["name", "title"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY name ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator
}, {
title: 'uses default limit if only offset is specified',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10000000000000;",
context: QueryGenerator
}, {
title: 'multiple where arguments',
arguments: ['myTable', {where: {boat: 'canoe', weather: 'cold'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat`='canoe' AND `myTable`.`weather`='cold';",
context: QueryGenerator
}, {
title: 'no where arguments (object)',
arguments: ['myTable', {where: {}}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (string)',
arguments: ['myTable', {where: ''}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (null)',
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}
],
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', { name: 'foo' }], arguments: ['myTable', { name: 'foo' }],
...@@ -257,6 +407,9 @@ if (dialect === 'sqlite') { ...@@ -257,6 +407,9 @@ if (dialect === 'sqlite') {
it(title, function(done) { it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation) expect(conditions).to.deep.equal(test.expectation)
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!