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

Commit fcd313b5 by Sascha Depold

Merge branch 'master' into milestones/2.0.0

Conflicts:
	lib/dao-factory.js
2 parents f7e0e1af 631616c1
...@@ -7,3 +7,5 @@ npm-debug.log ...@@ -7,3 +7,5 @@ npm-debug.log
*~ *~
test/binary/tmp/* test/binary/tmp/*
test/tmp/* test/tmp/*
test/sqlite/test.sqlite
coverage-*
...@@ -13,11 +13,15 @@ teaser: ...@@ -13,11 +13,15 @@ teaser:
test: test:
@if [ "$$GREP" ]; then \ @if [ "$$GREP" ]; then \
make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --check-leaks --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
else \ else \
make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi fi
cover:
rm -rf coverage \
make teaser && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -- -u exports --report lcovonly -- -R spec -- $(TESTS); \
mv coverage coverage-$(DIALECT) \
mariadb: mariadb:
@DIALECT=mariadb make test @DIALECT=mariadb make test
...@@ -32,6 +36,33 @@ postgres-native: ...@@ -32,6 +36,33 @@ postgres-native:
binary: binary:
@./test/binary/sequelize.test.bats @./test/binary/sequelize.test.bats
mariadb-cover:
rm -rf coverage
@DIALECT=mariadb make cover
sqlite-cover:
rm -rf coverage
@DIALECT=sqlite make cover
mysql-cover:
rm -rf coverage
@DIALECT=mysql make cover
postgres-cover:
rm -rf coverage
@DIALECT=postgres make cover
postgres-native-cover:
rm -rf coverage
@DIALECT=postgres-native make cover
binary-cover:
rm -rf coverage
@./test/binary/sequelize.test.bats
merge-coverage:
rm -rf coverage
mkdir coverage
./node_modules/.bin/lcov-result-merger 'coverage-*/lcov.info' 'coverage/lcov.info'
coveralls-send:
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage
# test aliases # test aliases
pgsql: postgres pgsql: postgres
...@@ -41,4 +72,7 @@ postgresn: postgres-native ...@@ -41,4 +72,7 @@ postgresn: postgres-native
all: sqlite mysql postgres postgres-native mariadb all: sqlite mysql postgres postgres-native mariadb
all-cover: sqlite-cover mysql-cover postgres-cover postgres-native-cover mariadb-cover merge-coverage
coveralls: sqlite-cover mysql-cover postgres-cover postgres-native-cover mariadb-cover merge-coverage coveralls-send
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test .PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
...@@ -58,7 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -58,7 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 1.7.0 ### 1.7.0
- ~~Check if lodash is a proper alternative to current underscore usage.~~ - ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions - ~~Transactions~~
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864) - Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key - Support for update of tables without primary key
- ~~MariaDB support~~ - ~~MariaDB support~~
...@@ -69,6 +69,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -69,6 +69,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- ~~Move validation of enum attribute value to validate method~~ Implemented in [#894](https://github.com/sequelize/sequelize/pull/894) thanks to @durango - ~~Move validation of enum attribute value to validate method~~ Implemented in [#894](https://github.com/sequelize/sequelize/pull/894) thanks to @durango
- ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier - ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
- Support for socket paths. [#982](https://github.com/sequelize/sequelize/issues/982)
### 1.7.x ### 1.7.x
- Complete support for non-id primary keys - Complete support for non-id primary keys
......
...@@ -34,19 +34,22 @@ var writeDefaultConfig = function(config) { ...@@ -34,19 +34,22 @@ var writeDefaultConfig = function(config) {
username: "root", username: "root",
password: null, password: null,
database: 'database_development', database: 'database_development',
host: '127.0.0.1' host: '127.0.0.1',
dialect: 'mysql'
}, },
test: { test: {
username: "root", username: "root",
password: null, password: null,
database: 'database_test', database: 'database_test',
host: '127.0.0.1' host: '127.0.0.1',
dialect: 'mysql'
}, },
production: { production: {
username: "root", username: "root",
password: null, password: null,
database: 'database_production', database: 'database_production',
host: '127.0.0.1' host: '127.0.0.1',
dialect: 'mysql'
} }
}, undefined, 2) + "\n" }, undefined, 2) + "\n"
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier - [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango - [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango
- [BUG] Don't delete foreign keys of many-to-many associations, if still needed. [#961](https://github.com/sequelize/sequelize/pull/961). thanks to sdepold - [BUG] Don't delete foreign keys of many-to-many associations, if still needed. [#961](https://github.com/sequelize/sequelize/pull/961). thanks to sdepold
- [BUG] Update timestamps when incrementing and decrementing [#1023](https://github.com/sequelize/sequelize/pull/1023). durango
- [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
...@@ -87,6 +88,8 @@ ...@@ -87,6 +88,8 @@
- [FEATURE] Model#findOrBuild. [#960](https://github.com/sequelize/sequelize/pull/960). Thanks to durango. - [FEATURE] Model#findOrBuild. [#960](https://github.com/sequelize/sequelize/pull/960). Thanks to durango.
- [FEATURE] Support for MariaDB. [#948](https://github.com/sequelize/sequelize/pull/948). Thanks to reedog117 and janmeier. - [FEATURE] Support for MariaDB. [#948](https://github.com/sequelize/sequelize/pull/948). Thanks to reedog117 and janmeier.
- [FEATURE] Filter through associations. [#991](https://github.com/sequelize/sequelize/pull/991). Thanks to snit-ram. - [FEATURE] Filter through associations. [#991](https://github.com/sequelize/sequelize/pull/991). Thanks to snit-ram.
- [FEATURE] Possibility to disable loging for .sync [#937](https://github.com/sequelize/sequelize/pull/937). Thanks to durango
- [FEATURE] Support for transactions. [1062](https://github.com/sequelize/sequelize/pull/1062).
- [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
......
...@@ -32,8 +32,8 @@ module.exports = (function() { ...@@ -32,8 +32,8 @@ module.exports = (function() {
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes and setters/getters to DAO prototype
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes) this.source.refreshAttributes()
return this return this
} }
...@@ -47,6 +47,7 @@ module.exports = (function() { ...@@ -47,6 +47,7 @@ module.exports = (function() {
obj[accessor] = function(params) { obj[accessor] = function(params) {
var id = this[self.identifier] var id = this[self.identifier]
, where = {} , where = {}
, options = Utils._.pick(params || {}, 'transaction')
where[primaryKey] = id where[primaryKey] = id
...@@ -60,7 +61,7 @@ module.exports = (function() { ...@@ -60,7 +61,7 @@ module.exports = (function() {
params = id params = id
} }
return self.target.find(params) return self.target.find(params, options)
} }
return this return this
...@@ -70,14 +71,15 @@ module.exports = (function() { ...@@ -70,14 +71,15 @@ module.exports = (function() {
var self = this var self = this
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))) , accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
obj[accessor] = function(associatedObject) { obj[accessor] = function(associatedObject, options) {
var primaryKeys = !!associatedObject && !!associatedObject.daoFactory ? Object.keys(associatedObject.daoFactory.primaryKeys) : [] var primaryKeys = !!associatedObject && !!associatedObject.daoFactory ? Object.keys(associatedObject.daoFactory.primaryKeys) : []
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
this[self.identifier] = associatedObject ? associatedObject[primaryKey] : null this[self.identifier] = associatedObject ? associatedObject[primaryKey] : null
options = Utils._.extend({ fields: [ self.identifier ], allowNull: [self.identifier] }, options)
// passes the changed field to save, so only that field get updated. // passes the changed field to save, so only that field get updated.
return this.save([ self.identifier ], {allowNull: [self.identifier]}) return this.save(options)
} }
return this return this
......
var Utils = require('./../utils') var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() { module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) { var HasManyDoubleLinked = function(definition, instance) {
...@@ -10,7 +11,9 @@ module.exports = (function() { ...@@ -10,7 +11,9 @@ module.exports = (function() {
} }
HasManyDoubleLinked.prototype.injectGetter = function(options) { HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this, _options = options var self = this
, _options = options
, smart
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {} var where = {}
...@@ -96,8 +99,14 @@ module.exports = (function() { ...@@ -96,8 +99,14 @@ module.exports = (function() {
, targetKeys = Object.keys(self.__factory.target.primaryKeys) , targetKeys = Object.keys(self.__factory.target.primaryKeys)
, obsoleteAssociations = [] , obsoleteAssociations = []
, changedAssociations = [] , changedAssociations = []
, options = {}
, unassociatedObjects; , unassociatedObjects;
if ((defaultAttributes || {}).transaction instanceof Transaction) {
options.transaction = defaultAttributes.transaction
delete defaultAttributes.transaction
}
unassociatedObjects = newAssociations.filter(function (obj) { unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id) return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
...@@ -130,15 +139,17 @@ module.exports = (function() { ...@@ -130,15 +139,17 @@ module.exports = (function() {
}) })
var where = {} var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where)) chainer.add(self.__factory.connectorDAO.destroy(where, options))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) { var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id) attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id) attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
...@@ -149,12 +160,12 @@ module.exports = (function() { ...@@ -149,12 +160,12 @@ module.exports = (function() {
return attributes return attributes
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk)) chainer.add(self.__factory.connectorDAO.bulkCreate(bulk, options))
} }
if (changedAssociations.length > 0) { if (changedAssociations.length > 0) {
changedAssociations.forEach(function (assoc) { changedAssociations.forEach(function (assoc) {
chainer.add(self.__factory.connectorDAO.update(assoc.attributes, assoc.where)) chainer.add(self.__factory.connectorDAO.update(assoc.attributes, assoc.where, options))
}) })
} }
......
var Utils = require('./../utils') var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() { module.exports = (function() {
var HasManySingleLinked = function(definition, instance) { var HasManySingleLinked = function(definition, instance) {
...@@ -9,6 +10,7 @@ module.exports = (function() { ...@@ -9,6 +10,7 @@ module.exports = (function() {
HasManySingleLinked.prototype.injectGetter = function(options) { HasManySingleLinked.prototype.injectGetter = function(options) {
var self = this var self = this
, where = {} , where = {}
, smart
options = options || {} options = options || {}
var primaryKey = Object.keys(this.instance.rawAttributes).filter(function(k) { return self.instance.rawAttributes[k].primaryKey === true }) var primaryKey = Object.keys(this.instance.rawAttributes).filter(function(k) { return self.instance.rawAttributes[k].primaryKey === true })
...@@ -28,11 +30,12 @@ module.exports = (function() { ...@@ -28,11 +30,12 @@ module.exports = (function() {
return this.__factory.target.all(options) return this.__factory.target.all(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations, defaultAttributes) {
var self = this var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {}) , associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id' , associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, options = {}
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return obj[associationKey] === old[associationKey] return obj[associationKey] === old[associationKey]
...@@ -45,6 +48,11 @@ module.exports = (function() { ...@@ -45,6 +48,11 @@ module.exports = (function() {
}) })
, update , update
if ((defaultAttributes || {}).transaction instanceof Transaction) {
options.transaction = defaultAttributes.transaction
delete defaultAttributes.transaction
}
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
// clear the old associations // clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) { var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
...@@ -54,12 +62,17 @@ module.exports = (function() { ...@@ -54,12 +62,17 @@ module.exports = (function() {
update = {} update = {}
update[self.__factory.identifier] = null update[self.__factory.identifier] = null
var primaryKeys = Object.keys(this.__factory.target.primaryKeys) var primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id' , primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, updateWhere = {} , updateWhere = {}
updateWhere[primaryKey] = obsoleteIds updateWhere[primaryKey] = obsoleteIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]})) chainer.add(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
...@@ -80,7 +93,12 @@ module.exports = (function() { ...@@ -80,7 +93,12 @@ module.exports = (function() {
update = {} update = {}
update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id) update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id)
updateWhere[primaryKey] = unassociatedIds updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]}))
chainer.add(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
))
} }
chainer chainer
......
...@@ -109,9 +109,9 @@ module.exports = (function() { ...@@ -109,9 +109,9 @@ module.exports = (function() {
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
} }
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes and setters/getters to DAO prototype
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes); this.target.refreshAttributes()
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes); this.source.refreshAttributes()
return this return this
} }
...@@ -124,11 +124,11 @@ module.exports = (function() { ...@@ -124,11 +124,11 @@ module.exports = (function() {
return new Class(self, this).injectGetter(options) return new Class(self, this).injectGetter(options)
} }
obj[this.accessors.hasAll] = function(objects) { obj[this.accessors.hasAll] = function(objects, options) {
var instance = this; var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]() instance[self.accessors.get](options)
.error(function(err){ customEventEmitter.emit('error', err)}) .error(function(err) { customEventEmitter.emit('error', err) })
.success(function(associatedObjects) { .success(function(associatedObjects) {
customEventEmitter.emit('success', customEventEmitter.emit('success',
Utils._.all(objects, function(o) { Utils._.all(objects, function(o) {
...@@ -144,10 +144,10 @@ module.exports = (function() { ...@@ -144,10 +144,10 @@ module.exports = (function() {
return customEventEmitter.run() return customEventEmitter.run()
} }
obj[this.accessors.hasSingle] = function(o) { obj[this.accessors.hasSingle] = function(o, options) {
var instance = this; var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]() instance[self.accessors.get](options)
.error(function(err){ customEventEmitter.emit('error', err)}) .error(function(err){ customEventEmitter.emit('error', err)})
.success(function(associatedObjects) { .success(function(associatedObjects) {
customEventEmitter.emit('success', customEventEmitter.emit('success',
......
...@@ -37,14 +37,14 @@ module.exports = (function() { ...@@ -37,14 +37,14 @@ module.exports = (function() {
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes and setters/getters to DAO prototype
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes); this.target.refreshAttributes()
return this return this
} }
HasOne.prototype.injectGetter = function(obj) { HasOne.prototype.injectGetter = function(obj) {
var self = this var self = this
, smart
obj[this.accessors.get] = function(params) { obj[this.accessors.get] = function(params) {
var primaryKeys = Object.keys(this.daoFactory.primaryKeys) var primaryKeys = Object.keys(this.daoFactory.primaryKeys)
...@@ -77,7 +77,7 @@ module.exports = (function() { ...@@ -77,7 +77,7 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) { HasOne.prototype.injectSetter = function(obj) {
var self = this var self = this
obj[this.accessors.set] = function(associatedObject) { obj[this.accessors.set] = function(associatedObject, options) {
var instance = this var instance = this
, instanceKeys = Object.keys(instance.daoFactory.primaryKeys) , instanceKeys = Object.keys(instance.daoFactory.primaryKeys)
, instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id' , instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id'
...@@ -86,11 +86,18 @@ module.exports = (function() { ...@@ -86,11 +86,18 @@ module.exports = (function() {
instance[self.accessors.get]().success(function(oldObj) { instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) { if (oldObj) {
oldObj[self.identifier] = null oldObj[self.identifier] = null
oldObj.save([self.identifier], {allowNull: [self.identifier]}).success(function() { oldObj
.save(
Utils._.extend({}, options, {
fields: [self.identifier],
allowNull: [self.identifier]
})
)
.success(function() {
if (associatedObject) { if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey] associatedObject[self.identifier] = instance[instanceKey]
associatedObject associatedObject
.save() .save(options)
.success(function() { emitter.emit('success', associatedObject) }) .success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) }) .error(function(err) { emitter.emit('error', err) })
} else { } else {
...@@ -101,7 +108,7 @@ module.exports = (function() { ...@@ -101,7 +108,7 @@ module.exports = (function() {
if (associatedObject) { if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey] associatedObject[self.identifier] = instance[instanceKey]
associatedObject associatedObject
.save() .save(options)
.success(function() { emitter.emit('success', associatedObject) }) .success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) }) .error(function(err) { emitter.emit('error', err) })
} else { } else {
......
...@@ -96,26 +96,32 @@ module.exports = (function() { ...@@ -96,26 +96,32 @@ module.exports = (function() {
// if an array with field names is passed to save() // if an array with field names is passed to save()
// only those fields will be updated // only those fields will be updated
DAO.prototype.save = function(fields, options) { DAO.prototype.save = function(fieldsOrOptions, options) {
if (fieldsOrOptions instanceof Array) {
fieldsOrOptions = { fields: fieldsOrOptions }
}
options = Utils._.extend({}, options, fieldsOrOptions)
var self = this var self = this
, values = fields ? {} : this.dataValues , values = options.fields ? {} : this.dataValues
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored) , updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored) , createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
if (fields) { if (options.fields) {
if (self.__options.timestamps) { if (self.__options.timestamps) {
if (fields.indexOf(updatedAtAttr) === -1) { if (options.fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr) options.fields.push(updatedAtAttr)
} }
if (fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) { if (options.fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) {
fields.push(createdAtAttr) options.fields.push(createdAtAttr)
} }
} }
var tmpVals = self.dataValues var tmpVals = self.dataValues
fields.forEach(function(field) { options.fields.forEach(function(field) {
if (tmpVals[field] !== undefined) { if (tmpVals[field] !== undefined) {
values[field] = tmpVals[field] values[field] = tmpVals[field]
} }
...@@ -134,9 +140,10 @@ module.exports = (function() { ...@@ -134,9 +140,10 @@ module.exports = (function() {
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString()) , isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = ['mysql', 'mariadb'].indexOf(self.daoFactory.daoFactoryManager.sequelize.options.dialect) !== -1 , isMySQL = ['mysql', 'mariadb'].indexOf(self.daoFactory.daoFactoryManager.sequelize.options.dialect) !== -1
, ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i) , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
, valueOutOfScope
// Unfortunately for MySQL CI collation we need to map/lowercase values again // Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation && (attrName in values)) { if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase()) var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1 valueOutOfScope = scopeIndex === -1
...@@ -172,7 +179,7 @@ module.exports = (function() { ...@@ -172,7 +179,7 @@ module.exports = (function() {
if (self.isNewRecord) { if (self.isNewRecord) {
self.isDirty = false self.isDirty = false
query = 'insert' query = 'insert'
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values] args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, options]
hook = 'Create' hook = 'Create'
} else { } else {
var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id } var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }
...@@ -224,7 +231,7 @@ module.exports = (function() { ...@@ -224,7 +231,7 @@ module.exports = (function() {
* *
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/ */
DAO.prototype.reload = function() { DAO.prototype.reload = function(options) {
var where = [ var where = [
this.QueryInterface.quoteIdentifier(this.__factory.tableName) + '.' + this.QueryInterface.quoteIdentifier('id')+'=?', this.QueryInterface.quoteIdentifier(this.__factory.tableName) + '.' + this.QueryInterface.quoteIdentifier('id')+'=?',
this.id this.id
...@@ -235,7 +242,7 @@ module.exports = (function() { ...@@ -235,7 +242,7 @@ module.exports = (function() {
where: where, where: where,
limit: 1, limit: 1,
include: this.__eagerlyLoadedOptions || [] include: this.__eagerlyLoadedOptions || []
}) }, options)
.on('sql', function(sql) { emitter.emit('sql', sql) }) .on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) }) .on('error', function(error) { emitter.emit('error', error) })
.on('success', function(obj) { .on('success', function(obj) {
...@@ -270,9 +277,14 @@ module.exports = (function() { ...@@ -270,9 +277,14 @@ module.exports = (function() {
return validator.hookValidate() return validator.hookValidate()
} }
DAO.prototype.updateAttributes = function(updates, fields) { DAO.prototype.updateAttributes = function(updates, fieldsOrOptions) {
if (fieldsOrOptions instanceof Array) {
fieldsOrOptions = { fields: fieldsOrOptions }
}
this.setAttributes(updates) this.setAttributes(updates)
return this.save(fields)
return this.save(fieldsOrOptions)
} }
DAO.prototype.setAttributes = function(updates) { DAO.prototype.setAttributes = function(updates) {
...@@ -314,7 +326,7 @@ module.exports = (function() { ...@@ -314,7 +326,7 @@ module.exports = (function() {
this.isDirty = isDirty this.isDirty = isDirty
} }
DAO.prototype.destroy = function() { DAO.prototype.destroy = function(options) {
var self = this var self = this
, query = null , query = null
...@@ -327,10 +339,10 @@ module.exports = (function() { ...@@ -327,10 +339,10 @@ module.exports = (function() {
if (self.__options.timestamps && self.__options.paranoid) { if (self.__options.timestamps && self.__options.paranoid) {
var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored) var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date() self.dataValues[attr] = new Date()
query = self.save() query = self.save(options)
} else { } else {
var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }; var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.__factory.tableName, self.__factory.options.schema), identifier) query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.__factory.tableName, self.__factory.options.schema), identifier, options)
} }
query.on('sql', function(sql) { query.on('sql', function(sql) {
...@@ -352,35 +364,73 @@ module.exports = (function() { ...@@ -352,35 +364,73 @@ module.exports = (function() {
}).run() }).run()
} }
DAO.prototype.increment = function(fields, count) { DAO.prototype.increment = function(fields, countOrOptions) {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }, Utils.validateParameter(countOrOptions, Object, {
values = {} optional: true,
deprecated: 'number',
deprecationWarning: "Increment expects an object as second parameter. Please pass the incrementor as option! ~> instance.increment(" + JSON.stringify(fields) + ", { by: " + countOrOptions + " })"
})
if (count === undefined) { var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
count = 1; , updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, values = {}
if (countOrOptions === undefined) {
countOrOptions = { by: 1, transaction: null }
} else if (typeof countOrOptions === 'number') {
countOrOptions = { by: countOrOptions, transaction: null }
} }
countOrOptions = Utils._.extend({
by: 1,
attributes: {}
}, countOrOptions)
if (Utils._.isString(fields)) { if (Utils._.isString(fields)) {
values[fields] = count; values[fields] = countOrOptions.by
} else if (Utils._.isArray(fields)) { } else if (Utils._.isArray(fields)) {
Utils._.each(fields, function (field) { Utils._.each(fields, function (field) {
values[field] = count values[field] = countOrOptions.by
}) })
} else { // Assume fields is key-value pairs } else { // Assume fields is key-value pairs
values = fields; values = fields
}
if (this.__options.timestamps) {
if (!values[updatedAtAttr]) {
countOrOptions.attributes[updatedAtAttr] = Utils.now(this.daoFactory.daoFactoryManager.sequelize.options.dialect)
}
} }
return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier) return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier, countOrOptions)
}
DAO.prototype.decrement = function (fields, countOrOptions) {
Utils.validateParameter(countOrOptions, Object, {
optional: true,
deprecated: 'number',
deprecationWarning: "Decrement expects an object as second parameter. Please pass the decrementor as option! ~> instance.decrement(" + JSON.stringify(fields) + ", { by: " + countOrOptions + " })"
})
if (countOrOptions === undefined) {
countOrOptions = { by: 1, transaction: null }
} else if (typeof countOrOptions === 'number') {
countOrOptions = { by: countOrOptions, transaction: null }
}
if (countOrOptions.by === undefined) {
countOrOptions.by = 1
} }
DAO.prototype.decrement = function (fields, count) {
if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
Utils._.each(fields, function (value, field) { Utils._.each(fields, function (value, field) {
fields[field] = -value; fields[field] = -value
}); })
} }
return this.increment(fields, 0 - count); countOrOptions.by = 0 - countOrOptions.by
return this.increment(fields, countOrOptions)
} }
DAO.prototype.equals = function(other) { DAO.prototype.equals = function(other) {
...@@ -411,43 +461,6 @@ module.exports = (function() { ...@@ -411,43 +461,6 @@ module.exports = (function() {
value = !!value 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'
}
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:
// calling __defineGetter__ destroys any previously defined setters for the attribute in
// question *if* that property setter was defined on the object's prototype (which is what
// we do in dao-factory) ... therefore we need to [re]define both the setter and getter
// here with either the function that already existed OR the default/automatic definition
//
// (the same is true for __defineSetter and 'prototype' getters)
if (has !== true) {
this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; });
this.__defineSetter__(attribute, has.set || function(v) {
if (Utils.hasChanged(this.dataValues[attribute], v)) {
//Only dirty the object if the change is not due to id, touchedAt, createdAt or updatedAt being initiated
var updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
, touchedAtAttr = Utils._.underscoredIf(this.__options.touchedAt, this.__options.underscored)
if (this.dataValues[attribute] || (attribute != 'id' && attribute != touchedAtAttr && attribute != createdAtAttr && attribute != updatedAtAttr)) {
this.isDirty = true
}
}
this.dataValues[attribute] = v
});
}
this[attribute] = value; this[attribute] = value;
} }
......
...@@ -188,6 +188,9 @@ module.exports = { ...@@ -188,6 +188,9 @@ module.exports = {
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB, BLOB: BLOB,
UUID: 'CHAR(36)',
UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4',
get ENUM() { get ENUM() {
var result = function() { var result = function() {
......
...@@ -454,6 +454,50 @@ module.exports = (function() { ...@@ -454,6 +454,50 @@ module.exports = (function() {
return query return query
}, },
/**
* Returns a query that starts a transaction.
*
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
* @return {String} The generated sql query.
*/
setAutocommitQuery: function(value) {
throwMethodUndefined('setAutocommitQuery')
},
setIsolationLevelQuery: function(value) {
throwMethodUndefined('setIsolationLevelQuery')
},
/**
* Returns a query that starts a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
startTransactionQuery: function(options) {
throwMethodUndefined('startTransactionQuery')
},
/**
* Returns a query that commits a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
commitTransactionQuery: function(options) {
throwMethodUndefined('commitTransactionQuery')
},
/**
* Returns a query that rollbacks a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
rollbackTransactionQuery: function(options) {
throwMethodUndefined('rollbackTransactionQuery')
},
addLimitAndOffset: function(options, query){ addLimitAndOffset: function(options, query){
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000; query += " LIMIT " + options.offset + ", " + 10000000000000;
...@@ -650,7 +694,7 @@ module.exports = (function() { ...@@ -650,7 +694,7 @@ module.exports = (function() {
result.push([_key, _value].join("=")) result.push([_key, _value].join("="))
} else { } else {
for (var logic in value) { for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic) var logicResult = Utils.getWhereLogic(logic, hash[key][logic]);
if (logic === "IN" || logic === "NOT IN") { if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]] var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')' _where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
......
...@@ -427,9 +427,14 @@ module.exports = (function() { ...@@ -427,9 +427,14 @@ module.exports = (function() {
for (var attrName in row) { for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) { if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || [] existingEntry[attrName] = existingEntry[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName]) existingEntry[attrName].push(row[attrName])
} }
} }
}
}) })
return result return result
......
...@@ -7,6 +7,10 @@ module.exports = (function(){ ...@@ -7,6 +7,10 @@ module.exports = (function(){
throw new Error('Define the query method!') throw new Error('Define the query method!')
} }
ConnectorManager.prototype.afterTransactionSetup = function(callback) {
callback()
}
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function() {
throw new Error('Define the connect method!') throw new Error('Define the connect method!')
} }
......
...@@ -20,7 +20,7 @@ module.exports = (function() { ...@@ -20,7 +20,7 @@ module.exports = (function() {
this.sql = sql this.sql = sql
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
} }
var resultSet = [], var resultSet = [],
...@@ -32,6 +32,8 @@ module.exports = (function() { ...@@ -32,6 +32,8 @@ module.exports = (function() {
.on('result', function(results) { .on('result', function(results) {
results results
.on('row', function(row, metadata) { .on('row', function(row, metadata) {
var type
for (var prop in row) { for (var prop in row) {
if (row.hasOwnProperty(prop)) { if (row.hasOwnProperty(prop)) {
if (row[prop] === null) { if (row[prop] === null) {
......
...@@ -161,13 +161,15 @@ module.exports = (function() { ...@@ -161,13 +161,15 @@ module.exports = (function() {
sql: sql sql: sql
}; };
enqueue.call(this, queueItem, options); queueItem.query.options.uuid = this.config.uuid
return queueItem.query; enqueue.call(this, queueItem, options)
return queueItem.query
} }
var self = this, query = new Query(this.client, this.sequelize, callee, options || {}); var self = this, query = new Query(this.client, this.sequelize, callee, options || {});
this.pendingQueries++; this.pendingQueries++;
query.options.uuid = this.config.uuid
query.done(function() { query.done(function() {
self.pendingQueries--; self.pendingQueries--;
if (self.pool) { if (self.pool) {
...@@ -277,7 +279,7 @@ module.exports = (function() { ...@@ -277,7 +279,7 @@ module.exports = (function() {
if (err) { if (err) {
switch(err.code) { switch(err.code) {
case 'ECONNREFUSED': case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR': case 'ER_ACCESS_D2ENIED_ERROR':
emitter.emit('error', 'Failed to authenticate for MySQL. Please double check your settings.') emitter.emit('error', 'Failed to authenticate for MySQL. Please double check your settings.')
break break
case 'ENOTFOUND': case 'ENOTFOUND':
......
...@@ -244,7 +244,7 @@ module.exports = (function() { ...@@ -244,7 +244,7 @@ module.exports = (function() {
return query return query
}, },
incrementQuery: function (tableName, attrValueHash, where) { incrementQuery: function (tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var values = [] var values = []
...@@ -256,6 +256,12 @@ module.exports = (function() { ...@@ -256,6 +256,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + _value) values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + _value)
} }
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var table = this.quoteIdentifier(tableName) var table = this.quoteIdentifier(tableName)
values = values.join(",") values = values.join(",")
where = this.getWhereConditions(where) where = this.getWhereConditions(where)
...@@ -327,6 +333,38 @@ module.exports = (function() { ...@@ -327,6 +333,38 @@ module.exports = (function() {
return Utils._.template(sql)({ tableName: tableName, indexName: indexName }) return Utils._.template(sql)({ tableName: tableName, indexName: indexName })
}, },
/**
* Returns a query that starts a transaction.
*
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
* @return {String} The generated sql query.
*/
setAutocommitQuery: function(value) {
return "SET autocommit = " + (!!value ? 1 : 0) + ";"
},
setIsolationLevelQuery: function(value) {
return "SET SESSION TRANSACTION ISOLATION LEVEL " + value + ";"
},
/**
* Returns a query that starts a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
startTransactionQuery: function(options) {
return "START TRANSACTION;"
},
commitTransactionQuery: function(options) {
return "COMMIT;"
},
rollbackTransactionQuery: function(options) {
return "ROLLBACK;"
},
attributesToSQL: function(attributes) { attributesToSQL: function(attributes) {
var result = {} var result = {}
......
...@@ -20,7 +20,7 @@ module.exports = (function() { ...@@ -20,7 +20,7 @@ module.exports = (function() {
this.sql = sql this.sql = sql
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
} }
this.client.query(this.sql, function(err, results, fields) { this.client.query(this.sql, function(err, results, fields) {
......
var Query = require("./query") var Query = require("./query")
, Utils = require("../../utils") , Utils = require("../../utils")
...@@ -60,7 +61,9 @@ module.exports = (function() { ...@@ -60,7 +61,9 @@ module.exports = (function() {
var query = new Query(self.client, self.sequelize, callee, options || {}) var query = new Query(self.client, self.sequelize, callee, options || {})
return query.run(sql) return query.run(sql)
.complete(function(err) { done && done(err) }) .complete(function(err) {
self.endQuery.call(self)
done && done(err) })
.success(function(results) { self.endQuery.call(self) }) .success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) }) .error(function(err) { self.endQuery.call(self) })
.proxy(emitter) .proxy(emitter)
...@@ -68,6 +71,10 @@ module.exports = (function() { ...@@ -68,6 +71,10 @@ module.exports = (function() {
}).run() }).run()
} }
ConnectorManager.prototype.afterTransactionSetup = function(callback) {
this.setTimezone(this.client, 'UTC', callback)
}
ConnectorManager.prototype.connect = function(callback) { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new (require('events').EventEmitter)() var emitter = new (require('events').EventEmitter)()
...@@ -106,12 +113,25 @@ module.exports = (function() { ...@@ -106,12 +113,25 @@ module.exports = (function() {
emitter.emit('error', err) emitter.emit('error', err)
break break
} }
} else {
emitter.emit('error', new Error(err.message))
} }
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'").on('end', function() { var timezoneCallback = function() {
self.isConnected = true self.isConnected = true
self.client = client self.client = client
emitter.emit('success', done) emitter.emit('success', done)
}
if (self.config.keepDefaultTimezone) {
Utils.tick(timezoneCallback)
} else {
self.setTimezone(client, 'UTC', timezoneCallback)
}
} else if (self.config.native) {
self.setTimezone(self.client, 'UTC', function() {
self.isConnected = true
emitter.emit('success', done)
}) })
} else { } else {
done && done() done && done()
...@@ -129,13 +149,19 @@ module.exports = (function() { ...@@ -129,13 +149,19 @@ module.exports = (function() {
} else { } else {
//create one-off client //create one-off client
this.client = new this.pg.Client(uri) this.client = new this.pg.Client(uri)
this.client.connect(connectCallback) this.client.connect(function(err, client, done) {
connectCallback(err, client || self.client, done)
})
} }
} }
return emitter return emitter
} }
ConnectorManager.prototype.setTimezone = function(client, timezone, callback) {
client.query("SET TIME ZONE '" + (timezone || "UTC") + "'").on('end', callback)
}
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
if (this.poolIdentifier) { if (this.poolIdentifier) {
this.poolIdentifier.destroyAllNow() this.poolIdentifier.destroyAllNow()
......
...@@ -145,10 +145,14 @@ module.exports = (function() { ...@@ -145,10 +145,14 @@ module.exports = (function() {
}, },
arrayValue: function(value, key, _key, factory){ arrayValue: function(value, key, _key, factory){
var col = null
, coltype = null
, _realKey = key.split('.').pop()
, _value
if (value.length === 0) { value = [null] } if (value.length === 0) { value = [null] }
var col = null, coltype = null
// Special conditions for searching within an array column type // Special conditions for searching within an array column type
var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) { if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey] col = factory.rawAttributes[_realKey]
coltype = col.type coltype = col.type
...@@ -366,7 +370,7 @@ module.exports = (function() { ...@@ -366,7 +370,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
incrementQuery: function(tableName, attrValueHash, where) { incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
...@@ -377,6 +381,12 @@ module.exports = (function() { ...@@ -377,6 +381,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + this.escape(value)) values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + this.escape(value))
} }
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = { var replacements = {
table: this.quoteIdentifiers(tableName), table: this.quoteIdentifiers(tableName),
values: values.join(","), values: values.join(","),
...@@ -870,7 +880,39 @@ module.exports = (function() { ...@@ -870,7 +880,39 @@ module.exports = (function() {
*/ */
dropForeignKeyQuery: function(tableName, foreignKey) { dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';' return 'ALTER TABLE ' + this.quoteIdentifier(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'
} },
/**
* Returns a query that starts a transaction.
*
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
* @return {String} The generated sql query.
*/
setAutocommitQuery: function(value) {
return "SET autocommit = " + (!!value ? 1 : 0) + ";"
},
setIsolationLevelQuery: function(value) {
return "SET SESSION TRANSACTION ISOLATION LEVEL " + value + ";"
},
/**
* Returns a query that starts a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
startTransactionQuery: function(options) {
return "START TRANSACTION;"
},
commitTransactionQuery: function(options) {
return "COMMIT;"
},
rollbackTransactionQuery: function(options) {
return "ROLLBACK;"
},
} }
// Private // Private
......
...@@ -27,7 +27,7 @@ module.exports = (function() { ...@@ -27,7 +27,7 @@ module.exports = (function() {
, rows = [] , rows = []
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
} }
query.on('row', function(row) { query.on('row', function(row) {
...@@ -57,7 +57,7 @@ module.exports = (function() { ...@@ -57,7 +57,7 @@ module.exports = (function() {
} }
var onSuccess = function(rows, sql) { var onSuccess = function(rows, sql) {
var results = [] var results = rows
, self = this , self = this
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
......
var Utils = require("../../utils") var Utils = require("../../utils")
, DataTypes = require("../../data-types") , DataTypes = require("../../data-types")
, SqlString = require("../../sql-string") , SqlString = require("../../sql-string")
, Transaction = require("../../transaction")
var MySqlQueryGenerator = Utils._.extend( var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../abstract/query-generator")), Utils._.clone(require("../abstract/query-generator")),
...@@ -81,8 +82,15 @@ module.exports = (function() { ...@@ -81,8 +82,15 @@ module.exports = (function() {
var length = dataType.match(/\(\s*\d+(\s*,\s*\d)?\s*\)/) var length = dataType.match(/\(\s*\d+(\s*,\s*\d)?\s*\)/)
if (length && length.index < modifierLastIndex) { if (length && length.index < modifierLastIndex) {
dataType = dataType.replace(length[0], '') dataType = dataType.replace(length[0], '')
dataType = Utils._.insert(dataType, modifierLastIndex, length[0])
// Since the legnth was placed before the modifier, removing the legnth has changed the index
if (length.index < modifierLastIndex) {
modifierLastIndex -= length[0].length
}
dataType = Utils._.insert(dataType, modifierLastIndex, length[0]).trim()
} }
modifierLastIndex = -1
} }
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) { if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
...@@ -214,7 +222,7 @@ module.exports = (function() { ...@@ -214,7 +222,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
incrementQuery: function(tableName, attrValueHash, where) { incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
...@@ -225,6 +233,12 @@ module.exports = (function() { ...@@ -225,6 +233,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + "+ " + this.escape(value)) values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + "+ " + this.escape(value))
} }
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = { var replacements = {
table: this.quoteIdentifier(tableName), table: this.quoteIdentifier(tableName),
values: values.join(","), values: values.join(","),
...@@ -396,6 +410,29 @@ module.exports = (function() { ...@@ -396,6 +410,29 @@ module.exports = (function() {
}) })
}, },
startTransactionQuery: function(options) {
return "BEGIN TRANSACTION;"
},
setAutocommitQuery: function(value) {
return "-- SQLite does not support SET autocommit."
},
setIsolationLevelQuery: function(value) {
switch (value) {
case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
return "-- SQLite is not able to choose the isolation level REPEATABLE READ."
case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
return "PRAGMA read_uncommitted = ON;"
case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
return "PRAGMA read_uncommitted = OFF;"
case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
return "-- SQLite's default isolation level is SERIALIZABLE. Nothing to do."
default:
throw new Error('Unknown isolation level: ' + value)
}
},
replaceBooleanDefaults: function(sql) { replaceBooleanDefaults: function(sql) {
return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1") return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1")
}, },
......
...@@ -26,19 +26,32 @@ module.exports = (function() { ...@@ -26,19 +26,32 @@ module.exports = (function() {
this.sql = sql this.sql = sql
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
} }
var columnTypes = {} var columnTypes = {}
this.database.serialize(function() { this.database.serialize(function() {
var executeSql = function() { var executeSql = function() {
if (self.sql.indexOf('-- ') === 0) {
// the sql query starts with a comment. don't bother the server with that ...
Utils.tick(function() {
self.emit('sql', self.sql)
self.emit('success', null)
})
} else {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) { self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
// allow clients to listen to sql to do their own logging or whatnot // allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql) self.emit('sql', self.sql)
this.columnTypes = columnTypes;
err ? onFailure.call(self, err) : onSuccess.call(self, results, this) if (err) {
onFailure.call(self, err)
} else {
this.columnTypes = columnTypes
onSuccess.call(self, results, this)
}
}) })
}; }
}
if ((getDatabaseMethod.call(self) === 'all') && /select\s.*?\sfrom\s+([^ ;]+)/i.test(self.sql)) { if ((getDatabaseMethod.call(self) === 'all') && /select\s.*?\sfrom\s+([^ ;]+)/i.test(self.sql)) {
var tableName = RegExp.$1; var tableName = RegExp.$1;
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise") , Promise = require("bluebird")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
, Utils = require('../utils') , Utils = require('../utils')
var bindToProcess = function(fct) { var bindToProcess = function(fct) {
...@@ -20,7 +19,7 @@ module.exports = (function() { ...@@ -20,7 +19,7 @@ module.exports = (function() {
util.inherits(CustomEventEmitter, EventEmitter) util.inherits(CustomEventEmitter, EventEmitter)
CustomEventEmitter.prototype.run = function() { CustomEventEmitter.prototype.run = function() {
tick(function() { Utils.tick(function() {
if (this.fct) { if (this.fct) {
this.fct.call(this, this) this.fct.call(this, this)
} }
......
...@@ -233,7 +233,7 @@ module.exports = (function() { ...@@ -233,7 +233,7 @@ module.exports = (function() {
// private // private
var getLastMigrationFromDatabase = function() { var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
...@@ -255,7 +255,7 @@ module.exports = (function() { ...@@ -255,7 +255,7 @@ module.exports = (function() {
}).run() }).run()
} }
var getLastMigrationIdFromDatabase = function() { var getLastMigrationIdFromDatabase = Migrator.prototype.getLastMigrationIdFromDatabase = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
...@@ -270,7 +270,7 @@ module.exports = (function() { ...@@ -270,7 +270,7 @@ module.exports = (function() {
}).run() }).run()
} }
var getFormattedDateString = function(s) { var getFormattedDateString = Migrator.prototype.getFormattedDateString = function(s) {
var result = null var result = null
try { try {
...@@ -282,11 +282,11 @@ module.exports = (function() { ...@@ -282,11 +282,11 @@ module.exports = (function() {
return result return result
} }
var stringToDate = function(s) { var stringToDate = Migrator.prototype.stringToDate = function(s) {
return moment(getFormattedDateString(s), "YYYYMMDDHHmmss") return moment(getFormattedDateString(s), "YYYYMMDDHHmmss")
} }
var saveSuccessfulMigration = function(from, to, callback) { var saveSuccessfulMigration = Migrator.prototype.saveSuccessfulMigration = function(from, to, callback) {
var self = this var self = this
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) { self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
...@@ -296,7 +296,7 @@ module.exports = (function() { ...@@ -296,7 +296,7 @@ module.exports = (function() {
}) })
} }
var deleteUndoneMigration = function(from, to, callback) { var deleteUndoneMigration = Migrator.prototype.deleteUndoneMigration = function(from, to, callback) {
var self = this var self = this
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) { self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
......
...@@ -75,6 +75,10 @@ module.exports = (function() { ...@@ -75,6 +75,10 @@ module.exports = (function() {
if (options.skipOnError && (self.fails.length > 0)) { if (options.skipOnError && (self.fails.length > 0)) {
onError('Skipped due to earlier error!') onError('Skipped due to earlier error!')
} else { } else {
if (typeof serial.options === "object" && Object.keys(serial.options).length > 0 && serial.method === "queryAndEmit") {
serial.params.push(serial.options)
}
var emitter = serial.klass[serial.method].apply(serial.klass, serial.params) var emitter = serial.klass[serial.method].apply(serial.klass, serial.params)
emitter.success(function(result) { emitter.success(function(result) {
......
...@@ -5,6 +5,8 @@ var url = require("url") ...@@ -5,6 +5,8 @@ var url = require("url")
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
, DAOFactoryManager = require("./dao-factory-manager") , DAOFactoryManager = require("./dao-factory-manager")
, QueryInterface = require("./query-interface") , QueryInterface = require("./query-interface")
, Transaction = require("./transaction")
, TransactionManager = require('./transaction-manager')
module.exports = (function() { module.exports = (function() {
/** /**
...@@ -72,6 +74,7 @@ module.exports = (function() { ...@@ -72,6 +74,7 @@ module.exports = (function() {
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null, dialectModulePath: null,
host: 'localhost', host: 'localhost',
port: 3306,
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
query: {}, query: {},
...@@ -109,14 +112,8 @@ module.exports = (function() { ...@@ -109,14 +112,8 @@ module.exports = (function() {
dialectOptions: this.options.dialectOptions, dialectOptions: this.options.dialectOptions,
} }
try {
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
} catch(err) {
throw new Error("The dialect " + this.options.dialect + " is not supported.")
}
this.daoFactoryManager = new DAOFactoryManager(this) this.daoFactoryManager = new DAOFactoryManager(this)
this.connectorManager = new ConnectorManager(this, this.config) this.transactionManager = new TransactionManager(this)
this.importCache = {} this.importCache = {}
} }
...@@ -131,6 +128,24 @@ module.exports = (function() { ...@@ -131,6 +128,24 @@ module.exports = (function() {
} }
/** /**
* Polyfill for the default connector manager.
*/
Object.defineProperty(Sequelize.prototype, 'connectorManager', {
get: function() {
return this.transactionManager.getConnectorManager()
}
})
/**
* Returns the specified dialect.
*
* @return {String} The specified dialect.
*/
Sequelize.prototype.getDialect = function() {
return this.options.dialect
}
/**
Returns an instance of QueryInterface. Returns an instance of QueryInterface.
@method getQueryInterface @method getQueryInterface
...@@ -288,7 +303,7 @@ module.exports = (function() { ...@@ -288,7 +303,7 @@ module.exports = (function() {
type: (sql.toLowerCase().indexOf('select') === 0) ? 'SELECT' : false type: (sql.toLowerCase().indexOf('select') === 0) ? 'SELECT' : false
}) })
return this.connectorManager.query(sql, callee, options) return this.transactionManager.query(sql, callee, options)
} }
Sequelize.prototype.createSchema = function(schema) { Sequelize.prototype.createSchema = function(schema) {
...@@ -330,6 +345,8 @@ module.exports = (function() { ...@@ -330,6 +345,8 @@ module.exports = (function() {
options = Utils._.extend({}, this.options.sync, options) options = Utils._.extend({}, this.options.sync, options)
} }
options.logging = options.logging === undefined ? false : Boolean(options.logging)
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
// Topologically sort by foreign key constraints to give us an appropriate // Topologically sort by foreign key constraints to give us an appropriate
...@@ -357,6 +374,22 @@ module.exports = (function() { ...@@ -357,6 +374,22 @@ module.exports = (function() {
}).run() }).run()
} }
Sequelize.prototype.authenticate = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self
.query('SELECT 1+1 AS result', null, { raw: true, plain: true })
.complete(function(err, result) {
if (!!err) {
emitter.emit('error', new Error('Invalid credentials.'))
} else {
emitter.emit('success')
}
})
}).run()
}
Sequelize.prototype.fn = function (fn) { Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1)) return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
} }
...@@ -373,5 +406,20 @@ module.exports = (function() { ...@@ -373,5 +406,20 @@ module.exports = (function() {
return new Utils.literal(val) return new Utils.literal(val)
} }
Sequelize.prototype.transaction = function(_options, _callback) {
var options = (typeof _options === 'function') ? {} : _options
, callback = (typeof _options === 'function') ? _options : _callback
, transaction = new Transaction(this, options)
, self = this
Utils.tick(function() {
transaction.prepareEnvironment(function() {
callback(transaction)
})
})
return transaction
}
return Sequelize return Sequelize
})() })()
...@@ -143,7 +143,7 @@ SqlString.format = function(sql, values, timeZone, dialect) { ...@@ -143,7 +143,7 @@ SqlString.format = function(sql, values, 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(/\:(?!\d)(\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 {
......
Utils = require('./utils')
var TransactionManager = module.exports = function(sequelize) {
this.sequelize = sequelize
this.connectorManagers = {}
try {
this.ConnectorManager = require("./dialects/" + sequelize.getDialect() + "/connector-manager")
} catch(err) {
throw new Error("The dialect " + sequelize.getDialect() + " is not supported.")
}
}
TransactionManager.prototype.getConnectorManager = function(uuid) {
uuid = uuid || 'default'
if (!this.connectorManagers.hasOwnProperty(uuid)) {
var config = Utils._.extend({ uuid: uuid }, this.sequelize.config)
if (uuid !== 'default') {
config.pool = { maxConnections: 0, useReplicaton: false }
config.keepDefaultTimezone = true
}
this.connectorManagers[uuid] = new this.ConnectorManager(this.sequelize, config)
}
return this.connectorManagers[uuid]
}
TransactionManager.prototype.query = function(sql, callee, options) {
options = options || {}
options.uuid = 'default'
if (options.transaction) {
options.uuid = options.transaction.id
}
return this.getConnectorManager(options.uuid).query(sql, callee, options)
}
var Utils = require('./utils')
, util = require('util')
var Transaction = module.exports = function(sequelize, options) {
this.sequelize = sequelize
this.id = Utils.generateUUID()
this.options = Utils._.extend({
autocommit: true,
isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
}, options || {})
}
util.inherits(Transaction, Utils.CustomEventEmitter)
Transaction.ISOLATION_LEVELS = {
READ_UNCOMMITTED: "READ UNCOMMITTED",
READ_COMMITTED: "READ COMMITTED",
REPEATABLE_READ: "REPEATABLE READ",
SERIALIZABLE: "SERIALIZABLE"
}
Transaction.prototype.commit = function() {
return this
.sequelize
.getQueryInterface()
.commitTransaction(this, {})
.proxy(this)
}
Transaction.prototype.rollback = function() {
return this
.sequelize
.getQueryInterface()
.rollbackTransaction(this, {})
.proxy(this)
}
Transaction.prototype.prepareEnvironment = function(callback) {
var self = this
, connectorManager = self.sequelize.transactionManager.getConnectorManager(this.id)
this.begin(function() {
self.setIsolationLevel(function() {
self.setAutocommit(function() {
connectorManager.afterTransactionSetup(callback)
})
})
})
}
Transaction.prototype.begin = function(callback) {
this
.sequelize
.getQueryInterface()
.startTransaction(this, {})
.success(callback)
}
Transaction.prototype.setAutocommit = function(callback) {
this
.sequelize
.getQueryInterface()
.setAutocommit(this, this.options.autocommit)
.success(callback)
}
Transaction.prototype.setIsolationLevel = function(callback) {
this
.sequelize
.getQueryInterface()
.setIsolationLevel(this, this.options.isolationLevel)
.success(callback)
.error(function(err) { console.log(err) })
}
...@@ -3,6 +3,8 @@ var util = require("util") ...@@ -3,6 +3,8 @@ var util = require("util")
, SqlString = require("./sql-string") , SqlString = require("./sql-string")
, lodash = require("lodash") , lodash = require("lodash")
, _string = require('underscore.string') , _string = require('underscore.string')
, ParameterValidator = require('./utils/parameter-validator')
, uuid = require('node-uuid')
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
...@@ -180,7 +182,7 @@ var Utils = module.exports = { ...@@ -180,7 +182,7 @@ var Utils = module.exports = {
} }
else if (type === "object") { else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) { Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii) logic = self.getWhereLogic(ii, where[i][ii]);
switch(logic) { switch(logic) {
case 'IN': case 'IN':
...@@ -282,7 +284,7 @@ var Utils = module.exports = { ...@@ -282,7 +284,7 @@ var Utils = module.exports = {
return Utils._.compactLite([text.join(' AND ')].concat(whereArgs)) return Utils._.compactLite([text.join(' AND ')].concat(whereArgs))
}, },
getWhereLogic: function(logic) { getWhereLogic: function(logic, val) {
switch (logic) { switch (logic) {
case 'join': case 'join':
return 'JOIN' return 'JOIN'
...@@ -297,7 +299,7 @@ var Utils = module.exports = { ...@@ -297,7 +299,7 @@ var Utils = module.exports = {
case 'eq': case 'eq':
return '=' return '='
case 'ne': case 'ne':
return '!=' return val ? '!=' : 'IS NOT'
case 'between': case 'between':
case '..': case '..':
return 'BETWEEN' return 'BETWEEN'
...@@ -375,8 +377,14 @@ var Utils = module.exports = { ...@@ -375,8 +377,14 @@ var Utils = module.exports = {
toDefaultValue: function(value) { toDefaultValue: function(value) {
if (lodash.isFunction(value)) { if (lodash.isFunction(value)) {
return value() return value()
} else if (value === DataTypes.UUIDV1) {
return uuid.v1()
} else if (value === DataTypes.UUIDV4) {
return uuid.v4()
} else if (value === DataTypes.NOW) {
return Utils.now()
} else { } else {
return (value === DataTypes.NOW) ? Utils.now() : value return value
} }
}, },
...@@ -394,6 +402,8 @@ var Utils = module.exports = { ...@@ -394,6 +402,8 @@ var Utils = module.exports = {
// have been normalized for this case // have been normalized for this case
if (value === DataTypes.NOW) {return false} if (value === DataTypes.NOW) {return false}
if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) {return false}
if (lodash.isFunction(value)) { if (lodash.isFunction(value)) {
return false return false
} }
...@@ -488,13 +498,17 @@ var Utils = module.exports = { ...@@ -488,13 +498,17 @@ var Utils = module.exports = {
return stack; 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)
return now return now
}, },
tick: function(func) {
var tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
tick(func)
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the // Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code. // `QueryInterface` instead for more portable code.
...@@ -524,6 +538,17 @@ var Utils = module.exports = { ...@@ -524,6 +538,17 @@ var Utils = module.exports = {
}, },
literal: function (val) { literal: function (val) {
this.val = val this.val = val
},
generateUUID: function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8)
return v.toString(16)
})
},
validateParameter: function(value, expectation, options) {
return ParameterValidator.check(value, expectation, options)
} }
} }
......
var cJSON = require('circular-json')
var ParameterValidator = module.exports = {
check: function(value, expectation, options) {
options = Utils._.extend({
throwError: true,
deprecated: false,
deprecationWarning: generateDeprecationWarning(value, expectation, options),
onDeprecated: function(s) { console.log('DEPRECATION WARNING:', s) },
index: null,
method: null,
optional: false
}, options || {})
if (options.optional && ((value === undefined) || (value === null)) ) {
return true
}
if (value === undefined) {
throw new Error('No value has been passed.')
}
if (expectation === undefined) {
throw new Error('No expectation has been passed.')
}
return false
|| validateDeprication(value, expectation, options)
|| validate(value, expectation, options)
}
}
var generateDeprecationWarning = function(value, expectation, options) {
options = options || {}
if (options.method && options.index) {
return [
'The',
{1:'first',2:'second',3:'third',4:'fourth',5:'fifth'}[options.index],
'parameter of',
options.method,
'should be a',
extractClassName(expectation) + '!'
].join(" ")
} else {
return ["Expected", cJSON.stringify(value), "to be", extractClassName(expectation) + '!'].join(" ")
}
}
var matchesExpectation = function(value, expectation) {
if (typeof expectation === 'string') {
return (typeof value === expectation.toString())
} else {
return (value instanceof expectation)
}
}
var validateDeprication = function(value, expectation, options) {
if (options.deprecated) {
if (matchesExpectation(value, options.deprecated)) {
options.onDeprecated(options.deprecationWarning)
return true
}
}
}
var validate = function(value, expectation, options) {
var result = matchesExpectation(value, expectation)
if (result) {
return result
} else if (!options.throwError) {
return false
} else {
var _value = (value === null) ? 'null' : value.toString()
, _expectation = extractClassName(expectation)
throw new Error('The parameter (value: ' + _value + ') is no ' + _expectation + '.')
}
}
var extractClassName = function(o) {
if (typeof o === 'string') {
return o
} else if (!!o) {
return o.toString().match(/function ([^\(]+)/)[1]
} else {
return 'undefined'
}
}
...@@ -40,25 +40,31 @@ ...@@ -40,25 +40,31 @@
"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.2.1", "moment": "~2.4.0",
"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",
"generic-pool": "2.0.4", "generic-pool": "2.0.4",
"promise": "~3.2.0", "sql": "~0.31.0",
"sql": "~0.28.0" "circular-json": "~0.1.5",
"bluebird": "~0.11.5",
"node-uuid": "~1.4.1"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha9", "mysql": "~2.0.0-alpha9",
"pg": "~2.6.0", "pg": "~2.8.1",
"watchr": "~2.4.3", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"chai": "~1.8.0", "chai": "~1.8.0",
"mocha": "~1.13.0", "mocha": "~1.13.0",
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3", "sinon": "~1.7.3",
"mariasql": "git://github.com/sequelize/node-mariasql.git" "mariasql": "git://github.com/mscdex/node-mariasql.git",
"chai-spies": "~0.5.1",
"lcov-result-merger": "0.0.2",
"istanbul": "~0.1.45",
"coveralls": "~2.5.0"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
......
...@@ -20,7 +20,66 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() { ...@@ -20,7 +20,66 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
}) })
}) })
describe('getAssociation', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
, Group = sequelize.define('Group', { name: Support.Sequelize.STRING })
Group.belongsTo(User)
sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Group.create({ name: 'bar' }).success(function(group) {
sequelize.transaction(function(t) {
group.setUser(user, { transaction: t }).success(function() {
Group.all().success(function(groups) {
groups[0].getUser().success(function(associatedUser) {
expect(associatedUser).to.be.null
Group.all({ transaction: t }).success(function(groups) {
groups[0].getUser({ transaction: t }).success(function(associatedUser) {
expect(associatedUser).to.be.not.null
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
})
})
})
describe('setAssociation', function() { describe('setAssociation', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
, Group = sequelize.define('Group', { name: Support.Sequelize.STRING })
Group.belongsTo(User)
sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Group.create({ name: 'bar' }).success(function(group) {
sequelize.transaction(function(t) {
group.setUser(user, { transaction: t }).success(function() {
Group.all().success(function(groups) {
groups[0].getUser().success(function(associatedUser) {
expect(associatedUser).to.be.null
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
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 })
, Task = this.sequelize.define('TaskXYZ', { task_id: {type: DataTypes.INTEGER, primaryKey: true }, title: DataTypes.STRING }) , Task = this.sequelize.define('TaskXYZ', { task_id: {type: DataTypes.INTEGER, primaryKey: true }, title: DataTypes.STRING })
......
...@@ -41,6 +41,37 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -41,6 +41,37 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var Article = sequelize.define('Article', { 'title': DataTypes.STRING })
, Label = sequelize.define('Label', { 'text': DataTypes.STRING })
Article.hasMany(Label)
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
sequelize.transaction(function(t) {
article.setLabels([ label ], { transaction: t }).success(function() {
Article.all({ transaction: t }).success(function(articles) {
articles[0].hasLabel(label).success(function(hasLabel) {
expect(hasLabel).to.be.false
Article.all({ transaction: t }).success(function(articles) {
articles[0].hasLabel(label, { transaction: t }).success(function(hasLabel) {
expect(hasLabel).to.be.true
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
})
})
it('does not have any labels assigned to it initially', function(done) { it('does not have any labels assigned to it initially', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([ var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Articl2e' }), this.Article.create({ title: 'Articl2e' }),
...@@ -106,6 +137,37 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -106,6 +137,37 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var Article = sequelize.define('Article', { 'title': DataTypes.STRING })
, Label = sequelize.define('Label', { 'text': DataTypes.STRING })
Article.hasMany(Label)
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
sequelize.transaction(function(t) {
article.setLabels([ label ], { transaction: t }).success(function() {
Article.all({ transaction: t }).success(function(articles) {
articles[0].hasLabels([ label ]).success(function(hasLabel) {
expect(hasLabel).to.be.false
Article.all({ transaction: t }).success(function(articles) {
articles[0].hasLabels([ label ], { transaction: t }).success(function(hasLabel) {
expect(hasLabel).to.be.true
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
})
})
it('answers false if only some labels have been assigned', function(done) { it('answers false if only some labels have been assigned', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([ var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }), this.Article.create({ title: 'Article' }),
...@@ -142,6 +204,38 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -142,6 +204,38 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
describe('setAssociations', function() { describe('setAssociations', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var Article = sequelize.define('Article', { 'title': DataTypes.STRING })
, Label = sequelize.define('Label', { 'text': DataTypes.STRING })
Article.hasMany(Label)
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
sequelize.transaction(function(t) {
article.setLabels([ label ], { transaction: t }).success(function() {
Label
.findAll({ where: { ArticleId: article.id }, transaction: undefined })
.success(function(labels) {
expect(labels.length).to.equal(0)
Label
.findAll({ where: { ArticleId: article.id }, transaction: t })
.success(function(labels) {
expect(labels.length).to.equal(1)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
it("clears associations when passing null to the set-method", function(done) { it("clears associations when passing null to the set-method", function(done) {
var User = this.sequelize.define('User', { username: DataTypes.STRING }) var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING }) , Task = this.sequelize.define('Task', { title: DataTypes.STRING })
...@@ -365,6 +459,38 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -365,6 +459,38 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var Article = sequelize.define('Article', { 'title': DataTypes.STRING })
, Label = sequelize.define('Label', { 'text': DataTypes.STRING })
Article.hasMany(Label)
Label.hasMany(Article)
sequelize.sync({ force: true }).success(function() {
Article.create({ title: 'foo' }).success(function(article) {
Label.create({ text: 'bar' }).success(function(label) {
sequelize.transaction(function(t) {
article.setLabels([ label ], { transaction: t }).success(function() {
Article.all({ transaction: t }).success(function(articles) {
articles[0].getLabels().success(function(labels) {
expect(labels).to.have.length(0)
Article.all({ transaction: t }).success(function(articles) {
articles[0].getLabels({ transaction: t }).success(function(labels) {
expect(labels).to.have.length(1)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
})
})
it("gets all associated objects when no options are passed", function(done) { it("gets all associated objects when no options are passed", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) { this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) { john.getTasks().success(function (tasks) {
...@@ -409,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -409,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User) Task.hasMany(User)
expect(Task.attributes.UserId).not.to.exist expect(Task.attributes.UserId).not.to.exist
setTimeout(function () {
done() done()
}, 50)
}) })
describe('setAssociations', function() { describe('setAssociations', function() {
......
...@@ -21,6 +21,37 @@ describe(Support.getTestDialectTeaser("HasOne"), function() { ...@@ -21,6 +21,37 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
}) })
describe('getAssocation', function() { describe('getAssocation', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
, Group = sequelize.define('Group', { name: Support.Sequelize.STRING })
Group.hasOne(User)
sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Group.create({ name: 'bar' }).success(function(group) {
sequelize.transaction(function(t) {
group.setUser(user, { transaction: t }).success(function() {
Group.all().success(function(groups) {
groups[0].getUser().success(function(associatedUser) {
expect(associatedUser).to.be.null
Group.all({ transaction: t }).success(function(groups) {
groups[0].getUser({ transaction: t }).success(function(associatedUser) {
expect(associatedUser).to.be.not.null
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
})
})
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 })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }) , Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING })
...@@ -44,9 +75,36 @@ describe(Support.getTestDialectTeaser("HasOne"), function() { ...@@ -44,9 +75,36 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
}) })
describe('setAssociation', function() { describe('setAssociation', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
, Group = sequelize.define('Group', { name: Support.Sequelize.STRING })
Group.hasOne(User)
sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Group.create({ name: 'bar' }).success(function(group) {
sequelize.transaction(function(t) {
group.setUser(user, { transaction: t }).success(function() {
Group.all().success(function(groups) {
groups[0].getUser().success(function(associatedUser) {
expect(associatedUser).to.be.null
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
})
it('can set an association with predefined primary keys', function(done) { it('can set an association with predefined primary keys', function(done) {
var User = this.sequelize.define('UserXYZZ', { userCoolIdTag: { type: Sequelize.INTEGER, primaryKey: true }, username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZZ', { userCoolIdTag: { type: Sequelize.INTEGER, primaryKey: true }, username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZZ', { taskOrSomething: { type: Sequelize.INTEGER, primaryKey: true }, title: Sequelize.STRING }) , Task = this.sequelize.define('TaskXYZZ', { taskOrSomething: { type: Sequelize.INTEGER, primaryKey: true }, title: Sequelize.STRING })
, self = this
User.hasOne(Task, {foreignKey: 'userCoolIdTag'}) User.hasOne(Task, {foreignKey: 'userCoolIdTag'})
......
...@@ -31,7 +31,7 @@ module.exports = { ...@@ -31,7 +31,7 @@ module.exports = {
postgres: { postgres: {
database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test', database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test',
username: process.env.SEQ_PG_USER || process.env.SEQ_USER || "postgres", username: process.env.SEQ_PG_USER || process.env.SEQ_USER || "postgres",
password: process.env.SEQ_PG_PW || process.env.SEQ_PW || null, password: process.env.SEQ_PG_PW || process.env.SEQ_PW || "postgres",
host: process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1', host: process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432, port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432,
pool: { pool: {
......
...@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.TEXT, 'TEXT', 'TEXT'], [Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'], [Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'], [Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.UUID, 'UUID', 'CHAR(36)'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'], [Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'], [Sequelize.BLOB, 'BLOB', 'BLOB'],
......
...@@ -101,7 +101,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -101,7 +101,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.bulkCreate([ this.User.bulkCreate([
{username: 'Bob', mood: 'cold'}, {username: 'Bob', mood: 'cold'},
{username: 'Tobi', mood: 'hot'} {username: 'Tobi', mood: 'hot'}
], [], {hooks: true}).success(function(bulkUsers) { ], { fields: [], hooks: true }).success(function(bulkUsers) {
expect(beforeBulkCreate).to.be.true expect(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.true expect(afterBulkCreate).to.be.true
expect(bulkUsers).to.be.instanceof(Array) expect(bulkUsers).to.be.instanceof(Array)
...@@ -268,7 +268,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -268,7 +268,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.bulkCreate([ this.User.bulkCreate([
{username: 'Bob', mood: 'cold'}, {username: 'Bob', mood: 'cold'},
{username: 'Tobi', mood: 'hot'} {username: 'Tobi', mood: 'hot'}
], null, {hooks: true}).success(function(bulkUsers) { ], { hooks: true }).success(function(bulkUsers) {
expect(beforeBulkCreate).to.be.true expect(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.true expect(afterBulkCreate).to.be.true
expect(bulkUsers).to.be.instanceof(Array) expect(bulkUsers).to.be.instanceof(Array)
...@@ -4320,7 +4320,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -4320,7 +4320,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
fn() fn()
}) })
this.User.bulkCreate([{aNumber: 5}, {aNumber: 7}, {aNumber: 3}], ['aNumber'], {hooks: true}).success(function(records) { this.User.bulkCreate([{aNumber: 5}, {aNumber: 7}, {aNumber: 3}], { fields: ['aNumber'], hooks: true }).success(function(records) {
records.forEach(function(record) { records.forEach(function(record) {
expect(record.username).to.equal('User' + record.id) expect(record.username).to.equal('User' + record.id)
expect(record.beforeHookTest).to.be.true expect(record.beforeHookTest).to.be.true
...@@ -4354,7 +4354,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -4354,7 +4354,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
fn() fn()
}) })
this.User.bulkCreate([{aNumber: 5}, {aNumber: 7}, {aNumber: 3}], ['aNumber'], {hooks: true}).error(function(err) { this.User.bulkCreate([{aNumber: 5}, {aNumber: 7}, {aNumber: 3}], { fields: ['aNumber'], hooks: true }).error(function(err) {
expect(err).to.equal('You shall not pass!') expect(err).to.equal('You shall not pass!')
expect(beforeBulkCreate).to.be.true expect(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.false expect(afterBulkCreate).to.be.false
...@@ -5272,7 +5272,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -5272,7 +5272,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
fn() fn()
}) })
this.User.bulkCreate([{aNumber: 1}, {aNumber: 1}, {aNumber: 1}], ['aNumber']).success(function() { this.User.bulkCreate([{aNumber: 1}, {aNumber: 1}, {aNumber: 1}], { fields: ['aNumber'] }).success(function() {
self.User.update({aNumber: 10}, {aNumber: 1}, {hooks: true}).error(function(err) { self.User.update({aNumber: 10}, {aNumber: 1}, {hooks: true}).error(function(err) {
expect(err).to.equal('You shall not pass!') expect(err).to.equal('You shall not pass!')
expect(beforeBulk).to.be.true expect(beforeBulk).to.be.true
...@@ -6059,7 +6059,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -6059,7 +6059,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
fn() fn()
}) })
this.User.bulkCreate([{aNumber: 1}, {aNumber: 1}, {aNumber: 1}], ['aNumber']).success(function() { this.User.bulkCreate([{aNumber: 1}, {aNumber: 1}, {aNumber: 1}], { fields: ['aNumber'] }).success(function() {
self.User.destroy({aNumber: 1}, {hooks: true}).error(function(err) { self.User.destroy({aNumber: 1}, {hooks: true}).error(function(err) {
expect(err).to.equal('You shall not pass!') expect(err).to.equal('You shall not pass!')
expect(beforeBulk).to.be.true expect(beforeBulk).to.be.true
...@@ -6087,6 +6087,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -6087,6 +6087,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}) })
it('on success', function(done) { it('on success', function(done) {
var beforeHook
, afterHook
this.User.beforeDelete(function(user, fn) { this.User.beforeDelete(function(user, fn) {
beforeHook = true beforeHook = true
fn() fn()
...@@ -6107,6 +6110,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -6107,6 +6110,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}) })
it('on error', function(done) { it('on error', function(done) {
var beforeHook
, afterHook
this.User.beforeDelete(function(user, fn) { this.User.beforeDelete(function(user, fn) {
beforeHook = true beforeHook = true
fn() fn()
...@@ -6142,6 +6148,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -6142,6 +6148,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}) })
it('on success', function(done) { it('on success', function(done) {
var beforeHook
, afterHook
this.User.hook('beforeDelete', function(user, fn) { this.User.hook('beforeDelete', function(user, fn) {
beforeHook = true beforeHook = true
fn() fn()
...@@ -6162,6 +6171,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -6162,6 +6171,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}) })
it('on error', function(done) { it('on error', function(done) {
var beforeHook
, afterHook
this.User.hook('beforeDelete', function(user, fn) { this.User.hook('beforeDelete', function(user, fn) {
beforeHook = true beforeHook = true
fn() fn()
......
...@@ -30,7 +30,10 @@ if (dialect.match(/^postgres/)) { ...@@ -30,7 +30,10 @@ if (dialect.match(/^postgres/)) {
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'}) Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'}) Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
setTimeout(function () {
done() done()
}, 50)
}) })
it("should not use a combined name", function(done) { it("should not use a combined name", function(done) {
......
...@@ -47,7 +47,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -47,7 +47,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
.find(1) .find(1)
.then(function(user) { .then(function(user) {
expect(user.id).to.equal(1) expect(user.id).to.equal(1)
return user.increment(['aNumber'], 2) return user.increment(['aNumber'], { by: 2 })
}) })
.then(function(user) { .then(function(user) {
// The following assertion would rock hard, but it's not implemented :( // The following assertion would rock hard, but it's not implemented :(
...@@ -72,7 +72,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -72,7 +72,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
.then(function (user2) { .then(function (user2) {
return user2 return user2
.updateAttributes({ aNumber: user2.aNumber + 1 }) .updateAttributes({ aNumber: user2.aNumber + 1 })
.then(function() { return user1.increment(['aNumber'], 2) }) .then(function() { return user1.increment(['aNumber'], { by: 2 }) })
.then(function() { return self.User.find(1) }) .then(function() { return self.User.find(1) })
.then(function(user5) { .then(function(user5) {
expect(user5.aNumber).to.equal(3) expect(user5.aNumber).to.equal(3)
...@@ -112,7 +112,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -112,7 +112,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
this.User this.User
.find(1) .find(1)
.then(function(user1) { .then(function(user1) {
return user1.decrement(['aNumber'], 2) return user1.decrement(['aNumber'], { by: 2 })
}) })
.then(function(user2) { .then(function(user2) {
return self.User.find(1) return self.User.find(1)
...@@ -129,7 +129,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -129,7 +129,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
this.User this.User
.find(1) .find(1)
.then(function(user1) { .then(function(user1) {
return user1.decrement(['aNumber'], 2) return user1.decrement(['aNumber'], { by: 2 })
}) })
.then(function(user3) { .then(function(user3) {
return self.User.find(1) return self.User.find(1)
...@@ -155,9 +155,9 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -155,9 +155,9 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
}) })
}) })
user1.decrement(['aNumber'], 2).done(_done) user1.decrement(['aNumber'], { by: 2 }).done(_done)
user1.decrement(['aNumber'], 2).done(_done) user1.decrement(['aNumber'], { by: 2 }).done(_done)
user1.decrement(['aNumber'], 2).done(_done) user1.decrement(['aNumber'], { by: 2 }).done(_done)
}) })
}) })
}) })
......
...@@ -91,8 +91,8 @@ if (dialect === 'sqlite') { ...@@ -91,8 +91,8 @@ if (dialect === 'sqlite') {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED'}], arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED PRIMARY KEY '}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5));" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5) PRIMARY KEY);"
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
......
var fs = require('fs') var fs = require('fs')
, path = require('path')
, Sequelize = require(__dirname + "/../index") , Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, Config = require(__dirname + "/config/config") , Config = require(__dirname + "/config/config")
...@@ -24,6 +25,19 @@ var Support = { ...@@ -24,6 +25,19 @@ var Support = {
}) })
}, },
prepareTransactionTest: function(sequelize, callback) {
var dialect = Support.getTestDialect()
if (dialect === 'sqlite') {
var options = Sequelize.Utils._.extend({}, sequelize.options, { storage: path.join(__dirname, 'tmp', 'db.sqlite') })
, _sequelize = new Sequelize(sequelize.config.datase, null, null, options)
_sequelize.sync({ force: true }).success(function() { callback(_sequelize) })
} else {
callback(sequelize)
}
},
createSequelizeInstance: function(options) { createSequelizeInstance: function(options) {
options = options || {} options = options || {}
options.dialect = options.dialect || 'mysql' options.dialect = options.dialect || 'mysql'
...@@ -49,6 +63,10 @@ var Support = { ...@@ -49,6 +63,10 @@ var Support = {
sequelizeOptions.define = options.define sequelizeOptions.define = options.define
} }
if (!!config.storage) {
sequelizeOptions.storage = config.storage
}
if (process.env.DIALECT === 'postgres-native') { if (process.env.DIALECT === 'postgres-native') {
sequelizeOptions.native = true sequelizeOptions.native = true
} }
......
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, Transaction = require(__dirname + '/../lib/transaction')
describe(Support.getTestDialectTeaser("Transaction"), function () {
describe('constructor', function() {
it('stores options', function() {
var transaction = new Transaction(this.sequelize)
expect(transaction.options).to.be.an.instanceOf(Object)
})
it('generates an identifier', function() {
var transaction = new Transaction(this.sequelize)
expect(transaction.id).to.exist
})
})
describe('commit', function() {
it('is a commit message available', function() {
expect(Transaction).to.respondTo('commit')
})
})
describe('rollback', function() {
it('is a rollback message available', function() {
expect(Transaction).to.respondTo('rollback')
})
})
describe('done', function() {
it('gets called when the transaction gets commited', function(done) {
var transaction = new Transaction(this.sequelize)
transaction.done(done)
transaction.prepareEnvironment(function() {
transaction.commit()
})
})
})
})
var chai = require('chai') var chai = require('chai')
, spies = require('chai-spies')
, expect = chai.expect , expect = chai.expect
, Utils = require(__dirname + '/../lib/utils') , Utils = require(__dirname + '/../lib/utils')
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
chai.use(spies)
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Utils"), function() { describe(Support.getTestDialectTeaser("Utils"), function() {
...@@ -158,4 +160,59 @@ describe(Support.getTestDialectTeaser("Utils"), function() { ...@@ -158,4 +160,59 @@ describe(Support.getTestDialectTeaser("Utils"), function() {
done() done()
}) })
}) })
describe('validateParameter', function() {
describe('method signature', function() {
it('throws an error if the value is not defined', function() {
expect(function() {
Utils.validateParameter()
}).to.throw('No value has been passed.')
})
it('does not throw an error if the value is not defined and the parameter is optional', function() {
expect(function() {
Utils.validateParameter(undefined, Object, { optional: true })
}).to.not.throw()
})
it('throws an error if the expectation is not defined', function() {
expect(function() {
Utils.validateParameter(1)
}).to.throw('No expectation has been passed.')
})
})
describe('expectation', function() {
it('uses the typeof method if the expectation is a string', function() {
expect(Utils.validateParameter(1, 'number')).to.be.true
})
it('uses the instanceof method if the expectation is a class', function() {
expect(Utils.validateParameter(new Number(1), Number)).to.be.true
})
})
describe('failing expectations', function() {
it('throws an error if the expectation does not match', function() {
expect(function() {
Utils.validateParameter(1, String)
}).to.throw(/The parameter.*is no.*/)
})
it('does not throw an error if throwError is false', function() {
expect(Utils.validateParameter(1, String, { throwError: false })).to.be.false
})
})
describe('deprecation warning', function() {
it('uses the passed function', function() {
var spy = chai.spy(function(s){})
Utils.validateParameter([], Object, {
deprecated: Array,
onDeprecated: spy
})
expect(spy).to.have.been.called()
})
})
})
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!