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

Commit 4df4b9b1 by Sascha Depold

Merge branch 'master' of github.com:sequelize/sequelize

2 parents 12572005 7dc35e38
......@@ -18,5 +18,6 @@
"unused": true,
"asi": true,
"evil": false,
"laxcomma": true
"laxcomma": true,
"es5": true
}
\ No newline at end of file
......@@ -7,6 +7,12 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa
## Important Notes ##
### 2.0.0 ###
There is a parallel "branch" of the project, released as `2.0.0-alphaX` in NPM. All those releases are based on the master
and will get all the changes of the master. However, `2.0.0` will contain backwards compatibility breaking changes. Check the
changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/2.0.0/changelog.md
### 1.6.0 ###
- We changed the way timestamps are handled. From v1.6.0 on timestamps are stored and loaded as UTC.
......@@ -236,7 +242,8 @@ for (var key in obj) {
"unused": true,
"asi": true,
"evil": false,
"laxcomma": true
"laxcomma": true,
"es5": true
}
```
......
......@@ -9,7 +9,9 @@
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [BUG] Fix where params for belongsTo [#658](https://github.com/sequelize/sequelize/pull/658). Thanks to mweibel
- [BUG] Default ports are now declared in the connector manager, which means the default port for PG correctly becomes 5432. [#633](https://github.com/sequelize/sequelize/issues/633)
- [BUG] Default ports are now declared in the connector manager, which means the default port for PG correctly becomes 5432. [#633](https://github.com/sequelize/sequelize/issues/633). durango
- [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
- [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] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
......@@ -23,6 +25,7 @@
- [FEATURE] `findOrCreate` now returns an additional flag (`created`), that is true if a model was created, and false if it was found [#648](https://github.com/sequelize/sequelize/pull/648). janmeier
- [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier
- [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos
- [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
# v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
......@@ -42,35 +42,52 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) {
var self = this
destroyObsoleteAssociations
.call(this, oldAssociations, newAssociations)
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
.error(function(err) { emitterProxy.emit('error', err) })
.success(function() {
var chainer = new Utils.QueryChainer
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id
})
})
unassociatedObjects.forEach(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = self.instance.id
attributes[foreignIdentifier] = unassociatedObject.id
chainer.add(self.__factory.connectorDAO.create(attributes))
, chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id
})
})
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id
})
})
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function (associatedObject) {
return associatedObject.id
})
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
chainer
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
var where = {}
where[self.__factory.identifier] = self.instance.id
where[foreignKey] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where))
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = self.instance.id
attributes[foreignIdentifier] = unassociatedObject.id
return attributes
})
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
}
chainer
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) {
......@@ -87,57 +104,5 @@ module.exports = (function() {
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
// private
var destroyObsoleteAssociations = function(oldAssociations, newAssociations) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
var foreignIdentifier = self.__factory.target.associations[self.__factory.associationAccessor].identifier
var obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id
})
})
if (obsoleteAssociations.length === 0) {
return emitter.emit('success', null)
}
obsoleteAssociations.forEach(function(associatedObject) {
var where = {}
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, notFoundEmitters = []
where[self.__factory.identifier] = self.instance.id
where[foreignKey] = associatedObject.id
self.__factory.connectorDAO
.find({ where: where })
.success(function(connector) {
if (connector === null) {
notFoundEmitters.push(null)
} else {
chainer.add(connector.destroy())
}
if ((chainer.emitters.length + notFoundEmitters.length) === obsoleteAssociations.length) {
// found all obsolete connectors and will delete them now
chainer
.run()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
}
})
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
})
}).run()
}
return HasManyDoubleLinked
})()
......@@ -7,7 +7,8 @@ module.exports = (function() {
}
HasManySingleLinked.prototype.injectGetter = function(options) {
var where = {}, options = options || {}
var where = {}
options = options || {}
where[this.__factory.identifier] = this.instance.id
......@@ -17,7 +18,6 @@ module.exports = (function() {
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this
, options = this.__factory.options
, chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) {
......@@ -29,23 +29,37 @@ module.exports = (function() {
return obj.id === old.id
})
})
, update
// clear the old associations
obsoleteAssociations.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = null
chainer.add(associatedObject.save())
})
if (obsoleteAssociations.length > 0) {
// clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = null
return associatedObject.id
})
// set the new associations
unassociatedObjects.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id
chainer.add(associatedObject.save())
})
update = {}
update[self.__factory.identifier] = null
chainer.add(this.__factory.target.update(update, { id: obsoleteIds }))
}
if (unassociatedObjects.length > 0) {
// set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id
return associatedObject.id
})
update = {}
update[self.__factory.identifier] = self.instance.id
chainer.add(this.__factory.target.update(update, { id: unassociatedIds }))
}
chainer
.run()
.success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
}
HasManySingleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) {
......
......@@ -2,6 +2,7 @@ var Utils = require("./utils")
, Mixin = require("./associations/mixin")
, DaoValidator = require("./dao-validator")
, DataTypes = require("./data-types")
, hstore = require('./dialects/postgres/hstore')
module.exports = (function() {
var DAO = function(values, options, isNewRecord) {
......@@ -130,15 +131,7 @@ module.exports = (function() {
if (isHstore) {
if (typeof values[attrName] === "object") {
var text = []
Utils._.each(values[attrName], function(value, key){
if (typeof value !== "string" && typeof value !== "number") {
throw new Error('Value for HSTORE must be a string or number.')
}
text.push(this.QueryInterface.quoteIdentifier(key) + '=>' + (typeof value === "string" ? this.QueryInterface.quoteIdentifier(value) : value))
}.bind(this))
values[attrName] = text.join(',')
values[attrName] = hstore.stringify(values[attrName])
}
}
}
......@@ -256,7 +249,9 @@ module.exports = (function() {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id },
values = {}
if (count === undefined) count = 1;
if (count === undefined) {
count = 1;
}
if (Utils._.isString(fields)) {
values[fields] = count;
......@@ -305,22 +300,25 @@ module.exports = (function() {
}
DAO.prototype.addAttribute = function(attribute, value) {
if (typeof this.dataValues[attribute] !== 'undefined')
return;
if (typeof this.dataValues[attribute] !== 'undefined') {
return
}
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1) // transform integer 0,1 into boolean
value = !!value;
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value !== undefined) { // transform integer 0,1 into boolean
value = !!value
}
var has = (function(o) {
var predef = Object.getOwnPropertyDescriptor(o, attribute);
if (predef && predef.hasOwnProperty('value'))
return true; // true here means 'this property exist as a simple value property, do not place setters or getters at all'
if (predef && predef.hasOwnProperty('value')) {
return true // true here means 'this property exist as a simple value property, do not place setters or getters at all'
}
return {
get: (predef && predef.hasOwnProperty('get') ? predef.get : null) || o.__lookupGetter__(attribute),
set: (predef && predef.hasOwnProperty('set') ? predef.set : null) || o.__lookupSetter__(attribute)
};
return {
get: (predef && predef.hasOwnProperty('get') ? predef.get : null) || o.__lookupGetter__(attribute),
set: (predef && predef.hasOwnProperty('set') ? predef.set : null) || o.__lookupSetter__(attribute)
};
})(this);
// @ node-v0.8.19:
......@@ -357,10 +355,6 @@ module.exports = (function() {
// add all passed values to the dao and store the attribute names in this.attributes
for (key in values) {
if (values.hasOwnProperty(key)) {
if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
values[key] = this.QueryInterface.QueryGenerator.toHstore(values[key])
}
this.addAttribute(key, values[key])
}
}
......@@ -368,8 +362,9 @@ module.exports = (function() {
if (isNewRecord) {
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key))
if (!defaults.hasOwnProperty(key)) {
defaults[key] = valueFn()
}
})
}
......
......@@ -43,7 +43,7 @@ module.exports = (function() {
charset: null
}, options || {})
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comment %> ENGINE=<%= engine %> <%= charset %>"
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comment %> ENGINE=<%= engine %> <%= charset %> <%= collation %>"
, primaryKeys = []
, foreignKeys = {}
, attrStr = []
......@@ -71,7 +71,8 @@ module.exports = (function() {
attributes: attrStr.join(", "),
comment: options.comment && Utils._.isString(options.comment) ? " COMMENT " + this.escape(options.comment) : "",
engine: options.engine,
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : ""),
collation: (options.collate ? "COLLATE " + options.collate : "")
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")
......@@ -431,7 +432,7 @@ module.exports = (function() {
result.push([_key, _value].join(" IN "))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) {
// is value an object?
//using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
......
module.exports = {
stringifyPart: function(part) {
switch(typeof part) {
case 'boolean':
case 'number':
return String(part)
case 'string':
return '"' + part.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
case 'undefined':
return 'NULL'
default:
if (part === null)
return 'NULL'
else
return '"' + JSON.stringify(part).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
}
},
stringify: function(data) {
var self = this
return Object.keys(data).map(function(key) {
return self.stringifyPart(key) + '=>' + self.stringifyPart(data[key])
}).join(',')
},
parsePart: function(part) {
part = part.replace(/\\\\/g, '\\').replace(/\\"/g, '"')
switch(part[0]) {
case '{':
case '[':
return JSON.parse(part)
default:
return part
}
},
parse: function(string) {
var self = this
const rx = /\"((?:\\\"|[^"])*)\"\s*\=\>\s*((?:true|false|NULL|\d+|\d+\.\d+|\"((?:\\\"|[^"])*)\"))/g
var object = { }
string.replace(rx, function(match, key, value, innerValue) {
switch(value) {
case 'true':
object[self.parsePart(key)] = true
break
case 'false':
object[self.parsePart(key)] = false
break
case 'NULL':
object[self.parsePart(key)] = null
break
default:
object[self.parsePart(key)] = self.parsePart(innerValue || value)
break
}
})
return object;
}
}
......@@ -292,7 +292,18 @@ module.exports = (function() {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
, returning = removeSerialsFromHash(tableName, attrValueHash)
// Remove serials that are null or undefined, which causes an error in PG
Utils._.forEach(attrValueHash, function(value, key, hash) {
if (tables[tableName]) {
switch (tables[tableName][key]) {
case 'bigserial':
case 'serial':
if ([null, undefined].indexOf(hash[key]) !== -1) delete hash[key]
break
}
}
});
var replacements = {
table: this.quoteIdentifiers(tableName)
......@@ -664,21 +675,6 @@ module.exports = (function() {
return matches.slice(0, -1)
},
toHstore: function(text) {
var obj = {}
, pattern = '("\\\\.|[^"\\\\]*"\s*=|[^=]*)\s*=\s*>\s*("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|$)'
, rex = new RegExp(pattern,'g')
, r = null
while ((r = rex.exec(text)) !== null) {
if (!!r[1] && !!r[2]) {
obj[r[1].replace(/^"/, '').replace(/"$/, '')] = r[2].replace(/^"/, '').replace(/"$/, '')
}
}
return obj
},
padInt: function (i) {
return (i < 10) ? '0' + i.toString() : i.toString()
},
......
var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types')
, hstore = require('./hstore')
module.exports = (function() {
var Query = function(client, sequelize, callee, options) {
......@@ -132,7 +133,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
record = hstore.parse(record)
}
this.callee[key] = record
}
......@@ -146,7 +147,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
record = hstore.parse(record)
}
this.callee[key] = record
}
......
......@@ -18,7 +18,10 @@
},
{
"name": "Jan Aagaard Meier",
"email": ["janzeh@gmail.com", "jmei@itu.dk"]
"email": [
"janzeh@gmail.com",
"jmei@itu.dk"
]
}
],
"repository": {
......
......@@ -77,17 +77,21 @@ describe('QueryGenerator', function() {
},
{
arguments: ['myTable', {title: "INTEGER COMMENT 'I\\'m a comment!'"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER COMMENT 'I\\'m a comment!') ENGINE=InnoDB;",
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER COMMENT 'I\\'m a comment!') ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: "INTEGER"}, {comment: "I'm a comment!"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;",
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'utf8', collate: 'utf8_unicode_ci'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
},
......
......@@ -3,6 +3,7 @@ if (typeof require === 'function') {
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
......@@ -32,8 +33,6 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
it('does not have any labels assigned to it initially', function(done) {
var self = this
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
......@@ -55,8 +54,6 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
it('answers true if the label has been assigned', function(done) {
var self = this
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
......@@ -90,8 +87,6 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
it('answers false if only some labels have been assigned', function(done) {
var self = this
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
......@@ -109,8 +104,6 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
it('answers true if all label have been assigned', function(done) {
var self = this
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
......@@ -186,7 +179,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
describe("getting assocations with options", function() {
before(function(done) {
var self = this;
var self = this
this.User = this.sequelize.define('User', { username: Sequelize.STRING })
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING, active: Sequelize.BOOLEAN })
......@@ -194,7 +187,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.User.hasMany(self.Task)
this.sequelize.sync({ force: true }).done(function() {
var chainer = new Sequelize.Utils.QueryChainer([
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
self.Task.create({ title: 'Die trying', active: false})
......@@ -210,7 +203,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) {
expect(tasks.length).toEqual(2)
done();
done()
})
})
})
......@@ -219,17 +212,66 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.User.find({ where: { username: 'John' } }).success(function (john) {
john.getTasks({ where: { active: true }, limit: 10, order: 'id DESC' }).success(function (tasks) {
expect(tasks.length).toEqual(1)
done();
done()
})
})
})
})
describe('optimizations using bulk create, destroy and update', function () {
before(function (done) {
this.User = this.sequelize.define('User', { username: Sequelize.STRING }, {timestamps: false})
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false})
this.User.hasMany(this.Task)
this.sequelize.sync({force: true}).success(done)
})
it('uses one UPDATE statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function(user) {
this.Task.create({ title: 'task1' }).success(function(task1) {
this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) { // We don't care about SELECt, only UPDAET
expect(sql).toMatch("UPDATE")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for UPDATE
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
it('uses one UPDATE statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function (user) {
this.Task.create({ title: 'task1' }).success(function (task1) {
this.Task.create({ title: 'task2' }).success(function (task2) {
user.setTasks([task1, task2]).success(function () {
user.setTasks(null).on('sql', spy).on('sql', _.after(2, function (sql) { // We don't care about SELECT, only UPDATE
expect(sql).toMatch("UPDATE")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for UPDATE
done()
})
})
}.bind(this))
}.bind(this))
}.bind(this))
})
}) // end optimization using bulk create, destroy and update
})
describe('(N:M)', function() {
describe("getting assocations with options", function() {
before(function(done) {
var self = this;
var self = this
this.User = this.sequelize.define('User', { username: Sequelize.STRING })
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING, active: Sequelize.BOOLEAN })
......@@ -238,7 +280,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
self.Task.hasMany(self.User)
this.sequelize.sync({ force: true }).done(function() {
var chainer = new Sequelize.Utils.QueryChainer([
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
self.Task.create({ title: 'Die trying', active: false})
......@@ -254,7 +296,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) {
expect(tasks.length).toEqual(2)
done();
done()
})
})
})
......@@ -263,7 +305,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks({where: {active: true}}).success(function (tasks) {
expect(tasks.length).toEqual(1)
done();
done()
})
})
})
......@@ -323,6 +365,56 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
})
})
describe('optimizations using bulk create, destroy and update', function () {
before(function (done) {
this.User = this.sequelize.define('User', { username: Sequelize.STRING }, {timestamps: false})
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false})
this.User.hasMany(this.Task)
this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(done)
})
it('uses one insert into statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function(user) {
this.Task.create({ title: 'task1' }).success(function(task1) {
this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("INSERT INTO")
expect(sql).toMatch("VALUES (1,1),(2,1)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
it('uses one delete from statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function (user) {
this.Task.create({ title: 'task1' }).success(function (task1) {
this.Task.create({ title: 'task2' }).success(function (task2) {
user.setTasks([task1, task2]).success(function () {
user.setTasks(null).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("DELETE FROM")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for DELETE
done()
})
})
}.bind(this))
}.bind(this))
}.bind(this))
})
}) // end optimization using bulk create, destroy and update
})
describe("Foreign key constraints", function() {
......@@ -480,5 +572,4 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
})
\ No newline at end of file
......@@ -518,6 +518,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
})
})
it('allows setting custom IDs', function (done) {
this.User.create({ id: 42 }).success(function (user) {
expect(user.id).toEqual(42)
this.User.find(42).success(function (user) {
expect(user).toBeDefined()
done()
})
}.bind(this))
})
describe('enums', function() {
before(function(done) {
this.Item = this.sequelize.define('Item', {
......@@ -849,6 +860,22 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
})
})
it("should not try to convert boolean values if they are not selected", function (done) {
var UserWithBoolean = this.sequelize.define('user', {
active: Sequelize.BOOLEAN
})
this.sequelize.sync({force: true}).success(function () {
UserWithBoolean.create({ active: true }).success(function (user) {
UserWithBoolean.find({ where: { id: user.id }, attributes: [ 'id' ] }).success(function (user) {
expect(user.active).not.toBeDefined()
done()
})
})
})
})
it('finds a specific user via where option', function(done) {
this.User.find({ where: { username: 'barfooz' } }).success(function(user) {
expect(user.username).toEqual('barfooz')
......
......@@ -19,7 +19,7 @@ if (dialect.match(/^postgres/)) {
self.User = sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
document: {type: DataTypes.HSTORE, defaultValue: 'default=>value'}
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
})
},
onComplete: function() {
......@@ -47,9 +47,10 @@ if (dialect.match(/^postgres/)) {
var self = this
this.User
.create({ username: 'user', email: ['foo@bar.com'], document: {hello: 'world'}})
.create({ username: 'user', email: ['foo@bar.com'], document: { created: { test: '"value"' }}})
.success(function(newUser) {
expect(newUser.document).toEqual({hello: 'world'})
expect(newUser.document).toEqual({ created: { test: '"value"' }})
// Check to see if updating an hstore field works
newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){
// Postgres always returns keys in alphabetical order (ascending)
......
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
if (dialect.match(/^postgres/)) {
describe('[POSTGRES] hstore', function() {
const hstore = require('../../lib/dialects/postgres/hstore')
describe('stringifyPart', function() {
it("handles undefined values correctly", function() {
expect(hstore.stringifyPart(undefined)).toEqual('NULL')
})
it("handles null values correctly", function() {
expect(hstore.stringifyPart(null)).toEqual('NULL')
})
it("handles boolean values correctly", function() {
expect(hstore.stringifyPart(false)).toEqual('false')
expect(hstore.stringifyPart(true)).toEqual('true')
})
it("handles strings correctly", function() {
expect(hstore.stringifyPart('foo')).toEqual('"foo"')
})
it("handles strings with backslashes correctly", function() {
expect(hstore.stringifyPart("\\'literally\\'")).toEqual('"\\\\\'literally\\\\\'"')
})
it("handles arrays correctly", function() {
expect(hstore.stringifyPart([1,['2'],'"3"'])).toEqual('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
})
it("handles simple objects correctly", function() {
expect(hstore.stringifyPart({ test: 'value' })).toEqual('"{\\"test\\":\\"value\\"}"')
})
it("handles nested objects correctly", function () {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).toEqual('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
})
it("handles objects correctly", function() {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).toEqual('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
})
})
describe('stringify', function() {
it('should handle empty objects correctly', function() {
expect(hstore.stringify({ })).toEqual('')
})
it('should handle null values correctly', function () {
expect(hstore.stringify({ null: null })).toEqual('"null"=>NULL')
})
it('should handle simple objects correctly', function() {
expect(hstore.stringify({ test: 'value' })).toEqual('"test"=>"value"')
})
it('should handle nested objects correctly', function() {
expect(hstore.stringify({ test: { nested: 'value' } })).toEqual('"test"=>"{\\"nested\\":\\"value\\"}"')
})
it('should handle nested arrays correctly', function() {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).toEqual('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).toEqual('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')
})
})
describe('parse', function() {
it('should handle empty objects correctly', function() {
expect(hstore.parse('')).toEqual({ })
})
it('should handle simple objects correctly', function() {
expect(hstore.parse('"test"=>"value"')).toEqual({ test: 'value' })
})
it('should handle nested objects correctly', function() {
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).toEqual({ test: { nested: 'value' } })
})
it('should handle nested arrays correctly', function() {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).toEqual({ test: [ 1, '2', [ '"3"' ] ] })
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).toEqual({ true: true, false: false, null: null, undefined: null, integer: 1, array: [1,'2'], object: { object: 'value' }})
})
})
})
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!