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

Commit ef5bfd20 by Sascha Depold

Merge branch 'master' into milestones/2.0.0

2 parents a8f6867c aadec576
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
- [BUG] Columns with type BOOLEAN were always added to toJSON output, even if they were not selected [see](https://gist.github.com/gchaincl/45aca14e93934bf4a05e). janmeier - [BUG] Columns with type BOOLEAN were always added to toJSON output, even if they were not selected [see](https://gist.github.com/gchaincl/45aca14e93934bf4a05e). janmeier
- [BUG] Hstore is now fully supported [#695](https://github.com/sequelize/sequelize/pull/695). thanks to tadman - [BUG] Hstore is now fully supported [#695](https://github.com/sequelize/sequelize/pull/695). thanks to tadman
- [BUG] Correct join table name for tables with custom names [#698](https://github.com/sequelize/sequelize/pull/698). thanks to jjclark1982 - [BUG] Correct join table name for tables with custom names [#698](https://github.com/sequelize/sequelize/pull/698). thanks to jjclark1982
- [BUG] PostgreSQL should now be able to insert empty arrays with typecasting. [#718](https://github.com/sequelize/sequelize/pull/718). thanks to durango
- [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango
- [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen
- [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
...@@ -34,6 +37,7 @@ ...@@ -34,6 +37,7 @@
- [FEATURE] Use moment for better postgres timestamp strings. [#710](https://github.com/sequelize/sequelize/pull/710). Thanks to seth-admittedly - [FEATURE] Use moment for better postgres timestamp strings. [#710](https://github.com/sequelize/sequelize/pull/710). Thanks to seth-admittedly
- [FEATURE] Keep milliseconds in timestamps for postgres. [#712](https://github.com/sequelize/sequelize/pull/712). Thanks to seth-admittedly - [FEATURE] Keep milliseconds in timestamps for postgres. [#712](https://github.com/sequelize/sequelize/pull/712). Thanks to seth-admittedly
- [FEATURE] You can now set lingo's language through Sequelize. [#713](https://github.com/sequelize/sequelize/pull/713). Thanks to durango - [FEATURE] You can now set lingo's language through Sequelize. [#713](https://github.com/sequelize/sequelize/pull/713). Thanks to durango
- [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
...@@ -297,16 +297,61 @@ module.exports = (function() { ...@@ -297,16 +297,61 @@ module.exports = (function() {
return this.QueryInterface.rawSelect(this.getTableName(), options, 'count') return this.QueryInterface.rawSelect(this.getTableName(), options, 'count')
} }
DAOFactory.prototype.findAndCountAll = function(options) {
var self = this
, opts = Utils._.cloneDeep(options)
, copts = Utils._.extend({}, Utils._.cloneDeep(options) || {}, {
// no limit, offset, order or include for the options given to count()
offset : 0,
limit : 0,
order : null,
include : null
})
return new Utils.CustomEventEmitter(function (emitter) {
var emit = {
err : function(e) { // emit error
emitter.emit('error', e);
}
, okay : function(c, r) { // emit success
emitter.emit('success', {
count: c || 0,
rows : (r && Array.isArray(r) ? r : [])
});
}
, sql : function(s) { // emit SQL
emitter.emit('sql', s);
}
}
self.count(copts)
.on('sql', emit.sql)
.error(emit.err)
.success(function(cnt) {
if (cnt === 0)
return emit.okay(cnt) // no records, no need for another query
self.findAll(options)
.on('sql', emit.sql)
.error(emit.err)
.success(function(rows) {
emit.okay(cnt, rows)
})
})
}).run()
}
DAOFactory.prototype.max = function(field, options) { DAOFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['max(' + field + ')', 'max']) options.attributes.push(['max(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'max'])
options.parseFloat = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'max') return this.QueryInterface.rawSelect(this.getTableName(), options, 'max')
} }
DAOFactory.prototype.min = function(field, options) { DAOFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + field + ')', 'min']) options.attributes.push(['min(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'min'])
options.parseFloat = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'min') return this.QueryInterface.rawSelect(this.getTableName(), options, 'min')
...@@ -396,7 +441,7 @@ module.exports = (function() { ...@@ -396,7 +441,7 @@ module.exports = (function() {
daos.forEach(function(dao) { daos.forEach(function(dao) {
var values = {}; var values = {};
fields.forEach(function(field) { fields.forEach(function(field) {
values[field] = dao.values[field] values[field] = dao.dataValues[field]
}) })
if (self.options.timestamps) { if (self.options.timestamps) {
values[updatedAtAttr] = Utils.now() values[updatedAtAttr] = Utils.now()
...@@ -406,7 +451,7 @@ module.exports = (function() { ...@@ -406,7 +451,7 @@ module.exports = (function() {
} else { } else {
daos.forEach(function(dao) { daos.forEach(function(dao) {
records.push(dao.values) records.push(dao.dataValues)
}) })
} }
......
...@@ -55,9 +55,9 @@ var validateAttributes = function() { ...@@ -55,9 +55,9 @@ var validateAttributes = function() {
var errors = {} var errors = {}
// for each field and value // for each field and value
Utils._.each(this.model.values, function(value, field) { Utils._.each(this.model.dataValues, function(value, field) {
var rawAttribute = this.model.rawAttributes[field] var rawAttribute = this.model.rawAttributes[field]
, hasAllowedNull = ((rawAttribute.allowNull === true) && ((value === null) || (value === undefined))) , hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) { if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) {
errors = Utils._.merge(errors, validateAttribute.call(this, value, field)) errors = Utils._.merge(errors, validateAttribute.call(this, value, field))
......
...@@ -93,7 +93,7 @@ module.exports = (function() { ...@@ -93,7 +93,7 @@ module.exports = (function() {
// only those fields will be updated // only those fields will be updated
DAO.prototype.save = function(fields) { DAO.prototype.save = function(fields) {
var self = this var self = this
, values = fields ? {} : this.values , values = fields ? {} : this.dataValues
, updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
...@@ -108,7 +108,7 @@ module.exports = (function() { ...@@ -108,7 +108,7 @@ module.exports = (function() {
} }
} }
var tmpVals = self.values var tmpVals = self.dataValues
fields.forEach(function(field) { fields.forEach(function(field) {
if (tmpVals[field] !== undefined) { if (tmpVals[field] !== undefined) {
...@@ -277,7 +277,7 @@ module.exports = (function() { ...@@ -277,7 +277,7 @@ module.exports = (function() {
DAO.prototype.equals = function(other) { DAO.prototype.equals = function(other) {
var result = true var result = true
Utils._.each(this.values, function(value, key) { Utils._.each(this.dataValues, function(value, key) {
if(Utils._.isDate(value) && Utils._.isDate(other[key])) { if(Utils._.isDate(value) && Utils._.isDate(other[key])) {
result = result && (value.getTime() == other[key].getTime()) result = result && (value.getTime() == other[key].getTime())
} else { } else {
......
...@@ -241,7 +241,9 @@ module.exports = (function() { ...@@ -241,7 +241,9 @@ module.exports = (function() {
} }
var connect = function(done, config) { var connect = function(done, config) {
var emitter = new (require('events').EventEmitter)()
config = config || this.config config = config || this.config
var connection = mysql.createConnection({ var connection = mysql.createConnection({
host: config.host, host: config.host,
port: config.port, port: config.port,
...@@ -250,6 +252,23 @@ module.exports = (function() { ...@@ -250,6 +252,23 @@ module.exports = (function() {
database: config.database, database: config.database,
timezone: 'Z' timezone: 'Z'
}) })
connection.connect(function(err) {
if (err) {
switch(err.code) {
case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR':
emitter.emit('error', new Error("Failed to authenticate for MySQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find MySQL server. Please double check your settings."))
break
}
}
})
connection.query("SET time_zone = '+0:00'"); connection.query("SET time_zone = '+0:00'");
// client.setMaxListeners(self.maxConcurrentQueries) // client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false this.isConnecting = false
......
...@@ -27,7 +27,8 @@ module.exports = (function() { ...@@ -27,7 +27,8 @@ module.exports = (function() {
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client == null) {
if (this.client === null) {
this.connect() this.connect()
} }
...@@ -68,7 +69,18 @@ module.exports = (function() { ...@@ -68,7 +69,18 @@ module.exports = (function() {
self.isConnecting = false self.isConnecting = false
if (!!err) { if (!!err) {
switch(err.code) {
case 'ECONNREFUSED':
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
emitter.emit('error', err) emitter.emit('error', err)
}
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'") client.query("SET TIME ZONE 'UTC'")
.on('end', function() { .on('end', function() {
......
...@@ -289,7 +289,7 @@ module.exports = (function() { ...@@ -289,7 +289,7 @@ module.exports = (function() {
return Utils._.template(query)(options) return Utils._.template(query)(options)
}, },
insertQuery: function(tableName, attrValueHash) { insertQuery: function(tableName, attrValueHash, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;" var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
...@@ -306,14 +306,17 @@ module.exports = (function() { ...@@ -306,14 +306,17 @@ module.exports = (function() {
} }
}); });
var rowValues = []
Object.keys(attrValueHash).forEach(function(attr) {
rowValues[rowValues.length] = this.escape(attrValueHash[attr], (!!attributes && !!attributes[attr] ? attributes[attr] : undefined))
}.bind(this))
var replacements = { var replacements = {
table: this.quoteIdentifiers(tableName) table: this.quoteIdentifiers(tableName)
, attributes: Object.keys(attrValueHash).map(function(attr){ , attributes: Object.keys(attrValueHash).map(function(attr){
return this.quoteIdentifier(attr) return this.quoteIdentifier(attr)
}.bind(this)).join(",") }.bind(this)).join(",")
, values: Utils._.values(attrValueHash).map(function(value){ , values: rowValues.join(",")
return this.escape(value)
}.bind(this)).join(",")
} }
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
...@@ -734,7 +737,7 @@ module.exports = (function() { ...@@ -734,7 +737,7 @@ module.exports = (function() {
return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.') return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
}, },
escape: function (val) { escape: function (val, field) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -746,7 +749,11 @@ module.exports = (function() { ...@@ -746,7 +749,11 @@ module.exports = (function() {
return val + ''; return val + '';
case 'object': case 'object':
if (Array.isArray(val)) { if (Array.isArray(val)) {
return 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']'; var ret = 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']'
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret
} }
} }
......
...@@ -5,17 +5,34 @@ var Utils = require("../../utils") ...@@ -5,17 +5,34 @@ var Utils = require("../../utils")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
this.database = db = new sqlite3.Database(sequelize.options.storage || ':memory:', function(err) { }
if(!err && sequelize.options.foreignKeys !== false) {
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.connect = function() {
var emitter = new (require('events').EventEmitter)()
, self = this
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) {
if (err.code === "SQLITE_CANTOPEN") {
emitter.emit('error', new Error("Failed to find SQL server. Please double check your settings."))
}
}
if(!err && self.sequelize.options.foreignKeys !== false) {
// Make it possible to define and use foreign key constraints unless // Make it possible to define and use foreign key constraints unless
// explicitly disallowed. It's still opt-in per relation // explicitly disallowed. It's still opt-in per relation
db.run('PRAGMA FOREIGN_KEYS=ON') db.run('PRAGMA FOREIGN_KEYS=ON')
} }
}) })
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.database) {
this.connect()
}
return new Query(this.database, this.sequelize, callee, options).run(sql) return new Query(this.database, this.sequelize, callee, options).run(sql)
} }
......
...@@ -254,7 +254,7 @@ module.exports = (function() { ...@@ -254,7 +254,7 @@ module.exports = (function() {
} }
QueryInterface.prototype.insert = function(dao, tableName, values) { QueryInterface.prototype.insert = function(dao, tableName, values) {
var sql = this.QueryGenerator.insertQuery(tableName, values) var sql = this.QueryGenerator.insertQuery(tableName, values, dao.daoFactory.rawAttributes)
return queryAndEmit.call(this, [sql, dao], 'insert', { return queryAndEmit.call(this, [sql, dao], 'insert', {
success: function(obj) { obj.isNewRecord = false } success: function(obj) { obj.isNewRecord = false }
}) })
......
...@@ -208,6 +208,74 @@ describe('DAOFactory', function() { ...@@ -208,6 +208,74 @@ describe('DAOFactory', function() {
}) })
}) })
describe('findAndCountAll', function() {
var users = [], fullcount = 3
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, fullcount)
})
it("handles where clause [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
it("handles where clause with ordering [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
/*
// at time of writing (v1.6.0) Sequelize does not seem to support 'offset' on it's own consistently (goes wrong for PostGRES and SQLite)
it("handles offset", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
*/
it("handles limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
it("handles offset and limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
})
describe('all', function() { describe('all', function() {
beforeEach(function() { beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2) Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
......
...@@ -385,7 +385,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -385,7 +385,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.Task.create({ title: 'task2' }).success(function(task2) { this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) { user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("INSERT INTO") expect(sql).toMatch("INSERT INTO")
expect(sql).toMatch("VALUES (1,1),(2,1)") expect(sql).toMatch("VALUES (1,1),(1,2)")
})).success(function () { })).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into
done() done()
......
...@@ -2,6 +2,7 @@ if(typeof require === 'function') { ...@@ -2,6 +2,7 @@ if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter") , CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, config = require(__dirname + "/config/config")
, dialect = Helpers.getTestDialect() , dialect = Helpers.getTestDialect()
} }
...@@ -11,6 +12,86 @@ buster.testRunner.timeout = 1000 ...@@ -11,6 +12,86 @@ buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index') var Sequelize = require(__dirname + '/../index')
describe(Helpers.getTestDialectTeaser("Configuration"), function() { describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it('should give us an error for not having the correct server details', function(done) {
var domain
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster!
done()
}
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
d.add(sequelize.query)
d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to find SQL server. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to find PostgresSQL server. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to find MySQL server. Please double check your settings.'
}
expect(err.message).toEqual(msg)
done()
})
d.run(function(){
sequelize.query('select 1 as hello')
.success(function(){})
})
})
it('should give us an error for not having the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") {
// This dialect doesn't support incorrect login information
expect('').toEqual('') // Silence Buster
return done()
}
var domain
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster!
done()
}
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
d.add(sequelize.query)
d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to authenticate for SQL. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to authenticate for MySQL. Please double check your settings.'
}
expect(err.message).toEqual(msg)
done()
})
d.run(function(){
sequelize.query('select 1 as hello')
.success(function(){})
})
})
})
describe('Instantiation with a URL string', function() { describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() { it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname') var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
......
...@@ -281,6 +281,26 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -281,6 +281,26 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}) })
describe('create', function() { describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').toEqual('')
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: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).toBeGreaterThan(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).toBeGreaterThan(-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', {
username: { type: Sequelize.STRING, unique: true } username: { type: Sequelize.STRING, unique: true }
...@@ -357,7 +377,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -357,7 +377,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) { User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).toEqual( '9223372036854775807' ) expect(user.big).toBe( '9223372036854775807' )
done() done()
}) })
}) })
...@@ -984,8 +1004,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -984,8 +1004,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
] ]
, done = _.after(2 * permutations.length, _done); , done = _.after(2 * permutations.length, _done);
this.User.create({name: 'jack'}).success(function (jack) { this.User.create({username: 'jack'}).success(function (jack) {
this.User.create({name: 'jill'}).success(function (jill) { this.User.create({username: 'jill'}).success(function (jill) {
permutations.forEach(function(perm) { permutations.forEach(function(perm) {
this.User.find(perm).done(function(err, user) { this.User.find(perm).done(function(err, user) {
expect(err).toBeNull(); expect(err).toBeNull();
...@@ -1635,7 +1655,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1635,7 +1655,8 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
describe('max', function() { describe('max', function() {
before(function(done) { before(function(done) {
this.UserWithAge = this.sequelize.define('UserWithAge', { this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER age: Sequelize.INTEGER,
order: Sequelize.INTEGER
}) })
this.UserWithDec = this.sequelize.define('UserWithDec', { this.UserWithDec = this.sequelize.define('UserWithDec', {
...@@ -1647,6 +1668,15 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1647,6 +1668,15 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}.bind(this)) }.bind(this))
}) })
it("should return the max value for a field named the same as an SQL reserved keyword", function(done) {
this.UserWithAge.create({age: 3, order: 5}).success(function(){
this.UserWithAge.max('order').success(function(max) {
expect(max).toEqual(5)
done()
})
}.bind(this))
})
it("should return the max value", function(done) { it("should return the max value", function(done) {
this.UserWithAge.create({ age: 2 }).success(function() { this.UserWithAge.create({ age: 2 }).success(function() {
this.UserWithAge.create({ age: 3 }).success(function() { this.UserWithAge.create({ age: 3 }).success(function() {
......
...@@ -444,6 +444,140 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -444,6 +444,140 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}) })
}) })
}) })
before(function(done) {
this.UserEager = this.sequelize.define('UserEagerLoadingSaves', {
username : Helpers.Sequelize.STRING,
age : Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.ProjectEager = this.sequelize.define('ProjectEagerLoadingSaves', {
title : Helpers.Sequelize.STRING,
overdue_days: Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.UserEager.hasMany(this.ProjectEager, { as: 'Projects' })
this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah' })
this.sequelize.sync({ force: true }).success(done)
})
it('saves one object that has a collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'joe', age: 1 }).success(function(user) {
this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).success(function(project1) {
this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).success(function(project2) {
user.setProjects([project1, project2]).success(function() {
this.UserEager.find({where: {age: 1}, include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(user) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(1)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
user.age = user.age + 1; // happy birthday joe
user.save().success(function(saveduser) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(2)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each a have collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'bart', age: 20 }).success(function(bart) {
this.UserEager.create({ username: 'lisa', age: 20 }).success(function(lisa) {
this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).success(function(detention1) {
this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).success(function(detention2) {
this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).success(function(exam1) {
this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).success(function(exam2) {
bart.setProjects([detention1, detention2]).success(function() {
lisa.setProjects([exam1, exam2]).success(function() {
this.UserEager.findAll({where: {age: 20}, order: 'username ASC', include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(simpsons) {
var _bart, _lisa;
expect(simpsons.length).toEqual(2)
_bart = simpsons[0];
_lisa = simpsons[1];
expect(_bart.projects).toBeDefined()
expect(_lisa.projects).toBeDefined()
expect(_bart.projects.length).toEqual(2)
expect(_lisa.projects.length).toEqual(2)
_bart.age = _bart.age + 1; // happy birthday bart - off to Moe's
_bart.save().success(function(savedbart) {
expect(savedbart.username).toEqual('bart')
expect(savedbart.age).toEqual(21)
_lisa.username = 'lsimpson';
_lisa.save().success(function(savedlisa) {
expect(savedlisa.username).toEqual('lsimpson')
expect(savedlisa.age).toEqual(20)
done()
})
})
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each has one eagerly loaded object (to which they belong)', function(done) {
this.UserEager.create({ username: 'poobah', age: 18 }).success(function(user) {
this.ProjectEager.create({ title: 'homework', overdue_days: 10 }).success(function(homework) {
this.ProjectEager.create({ title: 'party', overdue_days: 2 }).success(function(party) {
user.setProjects([homework, party]).success(function() {
this.ProjectEager.findAll({include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(projects) {
expect(projects.length).toEqual(2)
expect(projects[0].poobah).toBeDefined()
expect(projects[1].poobah).toBeDefined()
expect(projects[0].poobah.username).toEqual('poobah')
expect(projects[1].poobah.username).toEqual('poobah')
projects[0].title = 'partymore';
projects[1].title = 'partymore';
projects[0].overdue_days = 0;
projects[1].overdue_days = 0;
projects[0].save().success(function() {
projects[1].save().success(function() {
this.ProjectEager.findAll({where: {title: 'partymore', overdue_days: 0}, include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(savedprojects) {
expect(savedprojects.length).toEqual(2)
expect(savedprojects[0].poobah).toBeDefined()
expect(savedprojects[1].poobah).toBeDefined()
expect(savedprojects[0].poobah.username).toEqual('poobah')
expect(savedprojects[1].poobah.username).toEqual('poobah')
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
}) })
describe('toJSON', function toJSON() { describe('toJSON', function toJSON() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!