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

Commit 631616c1 by Mick Hansen

Merge pull request #1062 from sequelize/features/transactions

Transactions
2 parents 78e20e5c 5f805e3e
......@@ -7,4 +7,5 @@ npm-debug.log
*~
test/binary/tmp/*
test/tmp/*
test/sqlite/test.sqlite
coverage-*
......@@ -58,7 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 1.7.0
- ~~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)
- Support for update of tables without primary key
- ~~MariaDB support~~
......
......@@ -86,6 +86,7 @@
- [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] 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] 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
......
......@@ -45,8 +45,9 @@ module.exports = (function() {
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
obj[accessor] = function(params) {
var id = this[self.identifier]
, where = {}
var id = this[self.identifier]
, where = {}
, options = Utils._.pick(params || {}, 'transaction')
where[primaryKey] = id
......@@ -60,7 +61,7 @@ module.exports = (function() {
params = id
}
return self.target.find(params)
return self.target.find(params, options)
}
return this
......@@ -70,14 +71,15 @@ module.exports = (function() {
var self = this
, 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) : []
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
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.
return this.save([ self.identifier ], {allowNull: [self.identifier]})
return this.save(options)
}
return this
......
var Utils = require('./../utils')
var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) {
......@@ -48,14 +49,14 @@ module.exports = (function() {
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function (elem) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + elem) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + elem, true)
)
})
} else {
Utils._.forOwn(connectorDAO.rawAttributes, function (elem, key) {
options.attributes.push(
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifiers(connectorDAO.tableName + '.' + key) + ' as ' +
self.QueryInterface.quoteIdentifier(connectorDAO.name + '.' + key, true)
)
})
......@@ -90,16 +91,22 @@ module.exports = (function() {
}
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations, defaultAttributes) {
var self = this
, chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
var self = this
, chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, changedAssociations = []
, options = {}
, unassociatedObjects;
if ((defaultAttributes || {}).transaction instanceof Transaction) {
options.transaction = defaultAttributes.transaction
delete defaultAttributes.transaction
}
unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
......@@ -121,7 +128,7 @@ module.exports = (function() {
changedAssociation.where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
changedAssociation.where[foreignIdentifier] = newObj[foreignIdentifier] || newObj.id
changedAssociations.push(changedAssociation)
}
})
......@@ -132,31 +139,33 @@ module.exports = (function() {
})
var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where))
chainer.add(self.__factory.connectorDAO.destroy(where, options))
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
if (association.hasJoinTableModel) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.connectorDAO.name], defaultAttributes)
}
return attributes
})
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk, options))
}
if (changedAssociations.length > 0) {
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))
})
}
......@@ -191,7 +200,7 @@ module.exports = (function() {
this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
}
......
var Utils = require('./../utils')
var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() {
var HasManySingleLinked = function(definition, instance) {
......@@ -29,23 +30,29 @@ module.exports = (function() {
return this.__factory.target.all(options)
}
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer()
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations, defaultAttributes) {
var self = this
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer()
, options = {}
, obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) {
return obj[associationKey] === old[associationKey]
})
})
, unassociatedObjects = newAssociations.filter(function (obj) {
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return obj[associationKey] === old[associationKey]
})
})
, update
if ((defaultAttributes || {}).transaction instanceof Transaction) {
options.transaction = defaultAttributes.transaction
delete defaultAttributes.transaction
}
if (obsoleteAssociations.length > 0) {
// clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
......@@ -55,21 +62,26 @@ module.exports = (function() {
update = {}
update[self.__factory.identifier] = null
var primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, updateWhere = {}
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) {
// For the self.instance
var pkeys = Object.keys(self.instance.daoFactory.primaryKeys)
, pkey = pkeys.length === 1 ? pkeys[0] : 'id'
var pkeys = Object.keys(self.instance.daoFactory.primaryKeys)
, pkey = pkeys.length === 1 ? pkeys[0] : 'id'
// For chainer
, primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, updateWhere = {}
// set the new associations
......@@ -78,10 +90,15 @@ module.exports = (function() {
return associatedObject[associationKey]
})
update = {}
update = {}
update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id)
updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]}))
updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
))
}
chainer
......
......@@ -124,30 +124,30 @@ module.exports = (function() {
return new Class(self, this).injectGetter(options)
}
obj[this.accessors.hasAll] = function(objects) {
obj[this.accessors.hasAll] = function(objects, options) {
var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
.error(function(err){ customEventEmitter.emit('error', err)})
.success(function(associatedObjects) {
customEventEmitter.emit('success',
Utils._.all(objects, function(o) {
return Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
instance[self.accessors.get](options)
.error(function(err) { customEventEmitter.emit('error', err) })
.success(function(associatedObjects) {
customEventEmitter.emit('success',
Utils._.all(objects, function(o) {
return Utils._.any(associatedObjects, function(associatedObject) {
return Utils._.all(associatedObject.identifiers, function(key, identifier) {
return o[identifier] == associatedObject[identifier];
});
})
})
})
)
})
)
})
})
return customEventEmitter.run()
}
obj[this.accessors.hasSingle] = function(o) {
var instance = this;
obj[this.accessors.hasSingle] = function(o, options) {
var instance = this
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
instance[self.accessors.get](options)
.error(function(err){ customEventEmitter.emit('error', err)})
.success(function(associatedObjects) {
customEventEmitter.emit('success',
......
......@@ -77,31 +77,38 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) {
var self = this
obj[this.accessors.set] = function(associatedObject) {
var instance = this
obj[this.accessors.set] = function(associatedObject, options) {
var instance = this
, instanceKeys = Object.keys(instance.daoFactory.primaryKeys)
, instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id'
, instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id'
return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) {
oldObj[self.identifier] = null
oldObj.save([self.identifier], {allowNull: [self.identifier]}).success(function() {
if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey]
associatedObject
.save()
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
emitter.emit('success', null)
}
})
oldObj
.save(
Utils._.extend({}, options, {
fields: [self.identifier],
allowNull: [self.identifier]
})
)
.success(function() {
if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey]
associatedObject
.save(options)
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
emitter.emit('success', null)
}
})
} else {
if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey]
associatedObject
.save()
.save(options)
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
......
var Utils = require("./utils")
, DAO = require("./dao")
, DataTypes = require("./data-types")
, Util = require('util')
, sql = require('sql')
, SqlString = require('./sql-string')
var Utils = require("./utils")
, DAO = require("./dao")
, DataTypes = require("./data-types")
, Util = require('util')
, sql = require('sql')
, SqlString = require('./sql-string')
, Transaction = require('./transaction')
module.exports = (function() {
var DAOFactory = function(name, attributes, options) {
......@@ -92,8 +93,13 @@ module.exports = (function() {
return SqlString.format(query.text.replace(/(\$\d)/g, '?'), query.values, null, dialect) + ';'
}
result.exec = function() {
return self.QueryInterface.queryAndEmit([result.toSql(), self, { type: 'SELECT' }], 'snafu')
result.exec = function(options) {
options = Utils._.extend({
transaction: null,
type: 'SELECT'
}, options || {})
return self.QueryInterface.queryAndEmit([result.toSql(), self, options], 'snafu')
}
return result
......@@ -209,7 +215,7 @@ module.exports = (function() {
attributeManipulation[name][type] = fct
})
})
Object.defineProperties(this.DAO.prototype, attributeManipulation)
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes)
}
......@@ -218,9 +224,11 @@ module.exports = (function() {
options = Utils._.extend({}, this.options, options || {})
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var doQuery = function() {
self.QueryInterface
self
.QueryInterface
.createTable(self.getTableName(), self.attributes, options)
.success(function() { emitter.emit('success', self) })
.error(function(err) { emitter.emit('error', err) })
......@@ -228,7 +236,10 @@ module.exports = (function() {
}
if (options.force) {
self.drop(options).success(doQuery).error(function(err) { emitter.emit('error', err) })
self
.drop(options)
.success(doQuery)
.error(function(err) { emitter.emit('error', err) })
} else {
doQuery()
}
......@@ -376,7 +387,7 @@ module.exports = (function() {
return this.QueryInterface.select(this, this.tableName, options, Utils._.defaults({
type: 'SELECT',
hasJoin: hasJoin
}, queryOptions))
}, queryOptions, { transaction: (options || {}).transaction }))
}
//right now, the caller (has-many-double-linked) is in charge of the where clause
......@@ -389,7 +400,7 @@ module.exports = (function() {
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
}, queryOptions))
}, queryOptions, { transaction: (options || {}).transaction }))
}
/**
......@@ -555,19 +566,38 @@ module.exports = (function() {
return instance
}
DAOFactory.prototype.create = function(values, fields) {
return this.build(values).save(fields)
DAOFactory.prototype.create = function(values, fieldsOrOptions) {
Utils.validateParameter(values, Object, { optional: true })
Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'DAOFactory#create' })
if (fieldsOrOptions instanceof Array) {
fieldsOrOptions = { fields: fieldsOrOptions }
}
fieldsOrOptions = Utils._.extend({
transaction: null
}, fieldsOrOptions || {})
return this.build(values).save(fieldsOrOptions)
}
DAOFactory.prototype.findOrInitialize = DAOFactory.prototype.findOrBuild = function (params, defaults) {
DAOFactory.prototype.findOrInitialize = DAOFactory.prototype.findOrBuild = function (params, defaults, options) {
defaults = defaults || {}
options = options || {}
var self = this
, defaultKeys = Object.keys(defaults || {})
, defaultKeys = Object.keys(defaults)
, defaultLength = defaultKeys.length
if (!options.transaction && defaults.transaction && (defaults.transaction instanceof Transaction)) {
options.transaction = defaults.transaction
delete defaults.transaction
}
return new Utils.CustomEventEmitter(function (emitter) {
self.find({
where: params
}).success(function (instance) {
}, options).success(function (instance) {
if (instance === null) {
var i = 0
......@@ -592,10 +622,14 @@ module.exports = (function() {
}).run()
}
DAOFactory.prototype.findOrCreate = function (where, defaults) {
var self = this;
DAOFactory.prototype.findOrCreate = function (where, defaults, options) {
var self = this
, params = {}
options = Utils._.extend({
transaction: null
}, options || {})
var params = {};
for (var attrname in where) {
params[attrname] = where[attrname]
}
......@@ -603,13 +637,16 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function (emitter) {
self.find({
where: params
}, {
transaction: options.transaction
}).success(function (instance) {
if (instance === null) {
for (var attrname in defaults) {
params[attrname] = defaults[attrname]
}
self.create(params)
self
.create(params, options)
.success(function (instance) {
emitter.emit('success', instance, true)
})
......@@ -638,32 +675,39 @@ module.exports = (function() {
* generated IDs and other default values in a way that can be mapped to
* multiple records
*/
DAOFactory.prototype.bulkCreate = function(records, fields, options) {
options = options || {}
options.validate = options.validate === undefined ? false : Boolean(options.validate)
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
DAOFactory.prototype.bulkCreate = function(records, fieldsOrOptions, options) {
Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'DAOFactory#bulkCreate' })
Utils.validateParameter(options, 'undefined', { deprecated: Object, optional: true, index: 3, method: 'DAOFactory#bulkCreate' })
options = Utils._.extend({
validate: false,
hooks: false
}, options || {})
fields = fields || []
if (fieldsOrOptions instanceof Array) {
options.fields = fieldsOrOptions
} else {
options.fields = options.fields || []
options = Utils._.extend(options, fieldsOrOptions)
}
var self = this
, updatedAtAttr = Utils._.underscoredIf(self.options.updatedAt, self.options.underscored)
, createdAtAttr = Utils._.underscoredIf(self.options.createdAt, self.options.underscored)
, errors = []
, daos = records.map(function(v) {
return self.build(v)
})
, daos = records.map(function(v) { return self.build(v) })
return new Utils.CustomEventEmitter(function(emitter) {
var done = function() {
self.runHooks('afterBulkCreate', daos, fields, function(err, newRecords, newFields) {
self.runHooks('afterBulkCreate', daos, options.fields, function(err, newRecords, newFields) {
if (!!err) {
return emitter.emit('error', err)
}
daos = newRecords || daos
fields = newFields || fields
daos = newRecords || daos
options.fields = newFields || options.fields
emitter.emit('success', daos, fields)
emitter.emit('success', daos, options.fields)
})
}
......@@ -680,7 +724,7 @@ module.exports = (function() {
}
daos[i] = newValues || daos[i]
daos[i].save().error(function(err) {
daos[i].save({ transaction: options.transaction }).error(function(err) {
emitter.emit('error', err)
}).success(function() {
self.runHooks('afterCreate', daos[i], function(err, newValues) {
......@@ -708,9 +752,9 @@ module.exports = (function() {
records = []
daos.forEach(function(dao) {
var values = fields.length > 0 ? {} : dao.dataValues
var values = options.fields.length > 0 ? {} : dao.dataValues
fields.forEach(function(field) {
options.fields.forEach(function(field) {
values[field] = dao.dataValues[field]
})
......@@ -727,7 +771,7 @@ module.exports = (function() {
records.push(values)
})
self.QueryInterface.bulkInsert(self.tableName, records)
self.QueryInterface.bulkInsert(self.tableName, records, options)
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
......@@ -738,18 +782,18 @@ module.exports = (function() {
})
}
self.runHooks('beforeBulkCreate', daos, fields, function(err, newRecords, newFields) {
self.runHooks('beforeBulkCreate', daos, options.fields, function(err, newRecords, newFields) {
if (!!err) {
return emitter.emit('error', err)
}
daos = newRecords || daos
fields = newFields || fields
daos = newRecords || daos
options.fields = newFields || options.fields
if (options.validate === true) {
if (options.hooks === true) {
var iterate = function(i) {
daos[i].hookValidate({skip: fields}).error(function(err) {
daos[i].hookValidate({skip: options.fields}).error(function(err) {
errors[errors.length] = {record: v, errors: err}
i++
if (i > daos.length) {
......@@ -764,7 +808,7 @@ module.exports = (function() {
}
} else {
daos.forEach(function(v) {
var valid = v.validate({skip: fields})
var valid = v.validate({skip: options.fields})
if (valid !== null) {
errors[errors.length] = {record: v, errors: valid}
}
......
......@@ -96,26 +96,32 @@ module.exports = (function() {
// if an array with field names is passed to save()
// 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
, values = fields ? {} : this.dataValues
, values = options.fields ? {} : this.dataValues
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
if (fields) {
if (options.fields) {
if (self.__options.timestamps) {
if (fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr)
if (options.fields.indexOf(updatedAtAttr) === -1) {
options.fields.push(updatedAtAttr)
}
if (fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) {
fields.push(createdAtAttr)
if (options.fields.indexOf(createdAtAttr) === -1 && this.isNewRecord === true) {
options.fields.push(createdAtAttr)
}
}
var tmpVals = self.dataValues
fields.forEach(function(field) {
options.fields.forEach(function(field) {
if (tmpVals[field] !== undefined) {
values[field] = tmpVals[field]
}
......@@ -172,7 +178,7 @@ module.exports = (function() {
if (self.isNewRecord) {
self.isDirty = false
query = 'insert'
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values]
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.__factory), values, options]
hook = 'Create'
} else {
var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }
......@@ -224,7 +230,7 @@ module.exports = (function() {
*
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/
DAO.prototype.reload = function() {
DAO.prototype.reload = function(options) {
var where = [
this.QueryInterface.quoteIdentifier(this.__factory.tableName) + '.' + this.QueryInterface.quoteIdentifier('id')+'=?',
this.id
......@@ -235,7 +241,7 @@ module.exports = (function() {
where: where,
limit: 1,
include: this.__eagerlyLoadedOptions || []
})
}, options)
.on('sql', function(sql) { emitter.emit('sql', sql) })
.on('error', function(error) { emitter.emit('error', error) })
.on('success', function(obj) {
......@@ -273,9 +279,14 @@ module.exports = (function() {
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)
return this.save(fields)
return this.save(fieldsOrOptions)
}
DAO.prototype.setAttributes = function(updates) {
......@@ -317,7 +328,7 @@ module.exports = (function() {
this.isDirty = isDirty
}
DAO.prototype.destroy = function() {
DAO.prototype.destroy = function(options) {
var self = this
, query = null
......@@ -330,10 +341,10 @@ module.exports = (function() {
if (self.__options.timestamps && self.__options.paranoid) {
var attr = Utils._.underscoredIf(self.__options.deletedAt, self.__options.underscored)
self.dataValues[attr] = new Date()
query = self.save()
query = self.save(options)
} else {
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) {
......@@ -355,48 +366,73 @@ module.exports = (function() {
}).run()
}
DAO.prototype.increment = function(fields, count) {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, values = {}
, options = {}
DAO.prototype.increment = function(fields, countOrOptions) {
Utils.validateParameter(countOrOptions, Object, {
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 + " })"
})
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, values = {}
if (count === undefined) {
count = 1;
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)) {
values[fields] = count;
values[fields] = countOrOptions.by
} else if (Utils._.isArray(fields)) {
Utils._.each(fields, function (field) {
values[field] = count
values[field] = countOrOptions.by
})
} else { // Assume fields is key-value pairs
values = fields;
values = fields
}
if (this.__options.timestamps) {
if (!values[updatedAtAttr]) {
options[updatedAtAttr] = Utils.now(this.daoFactory.daoFactoryManager.sequelize.options.dialect)
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, options)
return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier, countOrOptions)
}
DAO.prototype.decrement = function (fields, count) {
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
}
if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
Utils._.each(fields, function (value, field) {
fields[field] = -value;
});
fields[field] = -value
})
}
if (count === undefined) {
count = 1;
}
countOrOptions.by = 0 - countOrOptions.by
return this.increment(fields, 0 - count);
return this.increment(fields, countOrOptions)
}
DAO.prototype.equals = function(other) {
......
......@@ -454,6 +454,50 @@ module.exports = (function() {
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){
if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000;
......
......@@ -7,6 +7,10 @@ module.exports = (function(){
throw new Error('Define the query method!')
}
ConnectorManager.prototype.afterTransactionSetup = function(callback) {
callback()
}
ConnectorManager.prototype.connect = function() {
throw new Error('Define the connect method!')
}
......
......@@ -20,7 +20,7 @@ module.exports = (function() {
this.sql = sql
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
}
var resultSet = [],
......@@ -67,7 +67,7 @@ module.exports = (function() {
case "MEDIUMBLOB":
case "LONGBLOB":
if (metadata.charsetNrs[prop] === 63) { // binary
row[prop] = new Buffer(row[prop])
row[prop] = new Buffer(row[prop])
}
break
case "TIME":
......@@ -104,7 +104,7 @@ module.exports = (function() {
} else if( /^show/.test(self.sql.toLowerCase()) ||
/^select/.test(self.sql.toLowerCase()) ||
/^describe/.test(self.sql.toLowerCase())) {
self.emit('success', self.formatResults(resultSet))
self.emit('success', self.formatResults(resultSet))
} else {
self.emit('success', self.formatResults(info))
}
......
......@@ -161,13 +161,15 @@ module.exports = (function() {
sql: sql
};
enqueue.call(this, queueItem, options);
return queueItem.query;
queueItem.query.options.uuid = this.config.uuid
enqueue.call(this, queueItem, options)
return queueItem.query
}
var self = this, query = new Query(this.client, this.sequelize, callee, options || {});
this.pendingQueries++;
query.options.uuid = this.config.uuid
query.done(function() {
self.pendingQueries--;
if (self.pool) {
......@@ -277,7 +279,7 @@ module.exports = (function() {
if (err) {
switch(err.code) {
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.')
break
case 'ENOTFOUND':
......
......@@ -333,6 +333,38 @@ module.exports = (function() {
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) {
var result = {}
......
......@@ -20,7 +20,7 @@ module.exports = (function() {
this.sql = sql
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) {
......
var Query = require("./query")
, Utils = require("../../utils")
......@@ -70,6 +71,10 @@ module.exports = (function() {
}).run()
}
ConnectorManager.prototype.afterTransactionSetup = function(callback) {
this.setTimezone(this.client, 'UTC', callback)
}
ConnectorManager.prototype.connect = function(callback) {
var self = this
var emitter = new (require('events').EventEmitter)()
......@@ -108,15 +113,23 @@ module.exports = (function() {
emitter.emit('error', err)
break
}
} else {
emitter.emit('error', new Error(err.message))
}
} else if (client) {
client.query("SET TIME ZONE 'UTC'").on('end', function() {
var timezoneCallback = function() {
self.isConnected = true
self.client = client
emitter.emit('success', done)
})
}
if (self.config.keepDefaultTimezone) {
Utils.tick(timezoneCallback)
} else {
self.setTimezone(client, 'UTC', timezoneCallback)
}
} else if (self.config.native) {
self.client.query("SET TIME ZONE 'UTC'").on('end', function() {
self.setTimezone(self.client, 'UTC', function() {
self.isConnected = true
emitter.emit('success', done)
})
......@@ -136,13 +149,19 @@ module.exports = (function() {
} else {
//create one-off client
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
}
ConnectorManager.prototype.setTimezone = function(client, timezone, callback) {
client.query("SET TIME ZONE '" + (timezone || "UTC") + "'").on('end', callback)
}
ConnectorManager.prototype.disconnect = function() {
if (this.poolIdentifier) {
this.poolIdentifier.destroyAllNow()
......
......@@ -880,7 +880,39 @@ module.exports = (function() {
*/
dropForeignKeyQuery: function(tableName, 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
......
......@@ -21,13 +21,13 @@ module.exports = (function() {
Query.prototype.run = function(sql) {
this.sql = sql
var self = this
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
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) {
......
var Utils = require("../../utils")
, DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
var Utils = require("../../utils")
, DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
, Transaction = require("../../transaction")
var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../abstract/query-generator")),
......@@ -409,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) {
return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1")
},
......
......@@ -26,24 +26,32 @@ module.exports = (function() {
this.sql = sql
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
this.options.logging('Executing (' + this.options.uuid + '): ' + this.sql)
}
var columnTypes = {}
this.database.serialize(function() {
var executeSql = function() {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
// allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql)
if (err) {
onFailure.call(self, err)
} else {
this.columnTypes = columnTypes
onSuccess.call(self, results, this)
}
})
};
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) {
// allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql)
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)) {
var tableName = RegExp.$1;
......
......@@ -2,7 +2,6 @@ var util = require("util")
, EventEmitter = require("events").EventEmitter
, Promise = require("bluebird")
, proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
, Utils = require('../utils')
var bindToProcess = function(fct) {
......@@ -20,7 +19,7 @@ module.exports = (function() {
util.inherits(CustomEventEmitter, EventEmitter)
CustomEventEmitter.prototype.run = function() {
tick(function() {
Utils.tick(function() {
if (this.fct) {
this.fct.call(this, this)
}
......
var Utils = require(__dirname + '/utils')
, DataTypes = require(__dirname + '/data-types')
, SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface')
, Transaction = require(__dirname + '/transaction')
module.exports = (function() {
var QueryInterface = function(sequelize) {
......@@ -112,8 +113,7 @@ module.exports = (function() {
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options)
chainer2.add(self.sequelize.query(sql, null, { raw: true, logging: options.logging }))
}
else if (!!results[enumIdx] && !!daoTable) {
} else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
......@@ -141,18 +141,19 @@ module.exports = (function() {
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
chainer2.run().success(function() {
queryAndEmit.call(self, sql, 'createTable', options)
.success(function(res) {
self.emit('createTable', null)
emitter.emit('success', res)
})
.error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
queryAndEmit
.call(self, sql, 'createTable', options)
.success(function(res) {
self.emit('createTable', null)
emitter.emit('success', res)
})
.error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).error(function(err) {
emitter.emit('error', err)
}).on('sql', function(sql) {
......@@ -449,16 +450,16 @@ module.exports = (function() {
return queryAndEmit.call(this, sql, "removeIndex")
}
QueryInterface.prototype.insert = function(dao, tableName, values) {
QueryInterface.prototype.insert = function(dao, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, dao.daoFactory.rawAttributes)
return queryAndEmit.call(this, [sql, dao], 'insert', {
return queryAndEmit.call(this, [sql, dao, options], 'insert', {
success: function(obj) { obj.isNewRecord = false }
})
}
QueryInterface.prototype.bulkInsert = function(tableName, records) {
QueryInterface.prototype.bulkInsert = function(tableName, records, options) {
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records)
return queryAndEmit.call(this, sql, 'bulkInsert')
return queryAndEmit.call(this, [sql, null, options], 'bulkInsert')
}
QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) {
......@@ -481,7 +482,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.add(self, 'queryAndEmit', [[sql, dao, options], 'delete'])
chainer.runSerially()
.success(function(results){
......@@ -507,7 +508,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate'])
chainer.add(self, 'queryAndEmit', [[sql, null, options], 'bulkUpdate'])
return chainer.runSerially()
.success(function(results){
......@@ -523,7 +524,7 @@ module.exports = (function() {
}).run()
}
QueryInterface.prototype.delete = function(dao, tableName, identifier) {
QueryInterface.prototype.delete = function(dao, tableName, identifier, options) {
var self = this
, restrict = false
, cascades = []
......@@ -592,7 +593,7 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.add(self, 'queryAndEmit', [[sql, dao, options], 'delete'])
chainer.runSerially()
.success(function(results){
......@@ -622,7 +623,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options])
chainer.add(self, 'queryAndEmit', [[sql, null, options], 'bulkDelete', options])
chainer.runSerially()
.success(function(results){
......@@ -663,8 +664,8 @@ module.exports = (function() {
}
QueryInterface.prototype.increment = function(dao, tableName, values, identifier, options) {
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier, options);
return queryAndEmit.call(this, [sql, dao], 'increment');
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier, options.attributes)
return queryAndEmit.call(this, [sql, dao, options], 'increment')
}
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) {
......@@ -675,10 +676,11 @@ module.exports = (function() {
}
return new Utils.CustomEventEmitter(function(emitter) {
var sql = self.QueryGenerator.selectQuery(tableName, options)
, qry = self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT' })
var sql = self.QueryGenerator.selectQuery(tableName, options)
, queryOptions = Utils._.extend({ transaction: options.transaction }, { plain: true, raw: true, type: 'SELECT' })
, query = self.sequelize.query(sql, null, queryOptions)
qry
query
.success(function(data) {
var result = data ? data[attributeSelector] : null
......@@ -804,6 +806,63 @@ module.exports = (function() {
return this.QueryGenerator.escape(value)
}
QueryInterface.prototype.setAutocommit = function(transaction, value) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set autocommit for a transaction without transaction object!')
}
var sql = this.QueryGenerator.setAutocommitQuery(value)
return this.queryAndEmit([sql, null, { transaction: transaction }], 'setAutocommit')
}
QueryInterface.prototype.setIsolationLevel = function(transaction, value) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set isolation level for a transaction without transaction object!')
}
var sql = this.QueryGenerator.setIsolationLevelQuery(value)
return this.queryAndEmit([sql, null, { transaction: transaction }], 'setIsolationLevel')
}
QueryInterface.prototype.startTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to start a transaction without transaction object!')
}
options = Utils._.extend({
transaction: transaction
}, options || {})
var sql = this.QueryGenerator.startTransactionQuery(options)
return this.queryAndEmit([sql, null, options], 'startTransaction')
}
QueryInterface.prototype.commitTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to commit a transaction without transaction object!')
}
options = Utils._.extend({
transaction: transaction
}, options || {})
var sql = this.QueryGenerator.commitTransactionQuery(options)
return this.queryAndEmit([sql, null, options], 'commitTransaction')
}
QueryInterface.prototype.rollbackTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to rollback a transaction without transaction object!')
}
options = Utils._.extend({
transaction: transaction
}, options || {})
var sql = this.QueryGenerator.rollbackTransactionQuery(options)
return this.queryAndEmit([sql, null, options], 'rollbackTransaction')
}
// private
var buildScope = function() {
......@@ -817,9 +876,10 @@ module.exports = (function() {
var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
options = Utils._.extend({
success: function(){},
error: function(){},
logging: this.sequelize.options.logging
success: function(){},
error: function(){},
transaction: null,
logging: this.sequelize.options.logging
}, options || {})
var execQuery = function(emitter) {
......@@ -834,27 +894,26 @@ module.exports = (function() {
sqlOrQueryParams.push(typeof options === "object" ? options : {})
}
query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
emitter.query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
} else {
query = this.sequelize.query(sqlOrQueryParams, null, options)
emitter.query = this.sequelize.query(sqlOrQueryParams, null, options)
}
// append the query for better testing
emitter.query = query
query.success(function(obj) {
options.success && options.success(obj)
this.emit(methodName, null)
emitter.emit('success', obj)
}.bind(this)).error(function(err) {
options.error && options.error(err)
this.emit(methodName, err)
emitter.emit('error', err)
}.bind(this))
query.on('sql', function(sql) {
emitter.emit('sql', sql)
})
emitter
.query
.success(function(obj) {
options.success && options.success(obj)
this.emit(methodName, null)
emitter.emit('success', obj)
}.bind(this))
.error(function(err) {
options.error && options.error(err)
this.emit(methodName, err)
emitter.emit('error', err)
}.bind(this))
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}.bind(this)
if (!!emitter) {
......
var url = require("url")
, Path = require("path")
, Utils = require("./utils")
, DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types')
, DAOFactoryManager = require("./dao-factory-manager")
, QueryInterface = require("./query-interface")
var url = require("url")
, Path = require("path")
, Utils = require("./utils")
, DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types')
, DAOFactoryManager = require("./dao-factory-manager")
, QueryInterface = require("./query-interface")
, Transaction = require("./transaction")
, TransactionManager = require('./transaction-manager')
module.exports = (function() {
/**
......@@ -72,6 +74,7 @@ module.exports = (function() {
dialect: 'mysql',
dialectModulePath: null,
host: 'localhost',
port: 3306,
protocol: 'tcp',
define: {},
query: {},
......@@ -108,15 +111,9 @@ module.exports = (function() {
maxConcurrentQueries: this.options.maxConcurrentQueries,
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.connectorManager = new ConnectorManager(this, this.config)
this.daoFactoryManager = new DAOFactoryManager(this)
this.transactionManager = new TransactionManager(this)
this.importCache = {}
}
......@@ -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.
@method getQueryInterface
......@@ -249,7 +264,7 @@ module.exports = (function() {
// make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName()
, callerPath = Path.dirname(callerFilename)
path = Path.resolve(callerPath, path)
}
......@@ -287,7 +302,7 @@ module.exports = (function() {
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) {
......@@ -358,6 +373,22 @@ module.exports = (function() {
}).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) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
}
......@@ -374,5 +405,20 @@ module.exports = (function() {
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
})()
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) })
}
var util = require("util")
, DataTypes = require("./data-types")
, SqlString = require("./sql-string")
, lodash = require("lodash")
, _string = require('underscore.string')
, uuid = require('node-uuid')
var util = require("util")
, DataTypes = require("./data-types")
, SqlString = require("./sql-string")
, lodash = require("lodash")
, _string = require('underscore.string')
, ParameterValidator = require('./utils/parameter-validator')
, uuid = require('node-uuid')
var Utils = module.exports = {
_: (function() {
......@@ -503,6 +504,11 @@ var Utils = module.exports = {
return now
},
tick: function(func) {
var tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
tick(func)
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code.
......@@ -532,6 +538,17 @@ var Utils = module.exports = {
},
literal: function (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,26 +40,28 @@
"underscore.string": "~2.3.0",
"lingo": "~0.0.5",
"validator": "~1.5.0",
"moment": "~2.2.1",
"moment": "~2.4.0",
"commander": "~2.0.0",
"dottie": "0.0.8-0",
"toposort-class": "~0.2.0",
"generic-pool": "2.0.4",
"sql": "~0.31.0",
"circular-json": "~0.1.5",
"bluebird": "~0.11.5",
"sql": "~0.28.0",
"node-uuid": "~1.4.1"
},
"devDependencies": {
"sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha9",
"pg": "~2.6.0",
"pg": "~2.8.1",
"watchr": "~2.4.3",
"yuidocjs": "~0.3.36",
"chai": "~1.8.0",
"mocha": "~1.13.0",
"chai-datetime": "~1.1.1",
"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"
......
......@@ -9,7 +9,7 @@ chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("BelongsTo"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.belongsTo(User)
......@@ -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() {
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) {
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 })
......@@ -207,15 +266,15 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
var User = this.sequelize.define('UserPKBT', {
username: {
type: DataTypes.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
......
......@@ -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) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Articl2e' }),
......@@ -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) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
......@@ -142,6 +204,38 @@ describe(Support.getTestDialectTeaser("HasMany"), 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) {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING })
......@@ -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) {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) {
......
......@@ -21,6 +21,37 @@ describe(Support.getTestDialectTeaser("HasOne"), 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) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING })
......@@ -44,9 +75,36 @@ describe(Support.getTestDialectTeaser("HasOne"), 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) {
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 })
, self = this
User.hasOne(Task, {foreignKey: 'userCoolIdTag'})
......@@ -244,15 +302,15 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
var User = this.sequelize.define('UserPKBT', {
username: {
type: Sequelize.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
......@@ -293,4 +351,4 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
})
})
})
\ No newline at end of file
})
......@@ -31,7 +31,7 @@ module.exports = {
postgres: {
database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test',
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',
port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432,
pool: {
......
......@@ -101,7 +101,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.bulkCreate([
{username: 'Bob', mood: 'cold'},
{username: 'Tobi', mood: 'hot'}
], [], {hooks: true}).success(function(bulkUsers) {
], { fields: [], hooks: true }).success(function(bulkUsers) {
expect(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.true
expect(bulkUsers).to.be.instanceof(Array)
......@@ -268,7 +268,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.bulkCreate([
{username: 'Bob', mood: 'cold'},
{username: 'Tobi', mood: 'hot'}
], null, {hooks: true}).success(function(bulkUsers) {
], { hooks: true }).success(function(bulkUsers) {
expect(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.true
expect(bulkUsers).to.be.instanceof(Array)
......@@ -4320,7 +4320,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
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) {
expect(record.username).to.equal('User' + record.id)
expect(record.beforeHookTest).to.be.true
......@@ -4354,7 +4354,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
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(beforeBulkCreate).to.be.true
expect(afterBulkCreate).to.be.false
......@@ -5272,7 +5272,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
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) {
expect(err).to.equal('You shall not pass!')
expect(beforeBulk).to.be.true
......@@ -6059,7 +6059,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
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) {
expect(err).to.equal('You shall not pass!')
expect(beforeBulk).to.be.true
......
......@@ -47,7 +47,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
.find(1)
.then(function(user) {
expect(user.id).to.equal(1)
return user.increment(['aNumber'], 2)
return user.increment(['aNumber'], { by: 2 })
})
.then(function(user) {
// The following assertion would rock hard, but it's not implemented :(
......@@ -72,7 +72,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
.then(function (user2) {
return user2
.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(user5) {
expect(user5.aNumber).to.equal(3)
......@@ -112,7 +112,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
this.User
.find(1)
.then(function(user1) {
return user1.decrement(['aNumber'], 2)
return user1.decrement(['aNumber'], { by: 2 })
})
.then(function(user2) {
return self.User.find(1)
......@@ -129,7 +129,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
this.User
.find(1)
.then(function(user1) {
return user1.decrement(['aNumber'], 2)
return user1.decrement(['aNumber'], { by: 2 })
})
.then(function(user3) {
return self.User.find(1)
......@@ -155,9 +155,9 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
})
})
user1.decrement(['aNumber'], 2).done(_done)
user1.decrement(['aNumber'], 2).done(_done)
user1.decrement(['aNumber'], 2).done(_done)
user1.decrement(['aNumber'], { by: 2 }).done(_done)
user1.decrement(['aNumber'], { by: 2 }).done(_done)
user1.decrement(['aNumber'], { by: 2 }).done(_done)
})
})
})
......
var chai = require('chai')
, expect = chai.expect
, assert = chai.assert
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
, Sequelize = require(__dirname + '/../index')
, config = require(__dirname + "/config/config")
, moment = require('moment')
, sinon = require('sinon')
var chai = require('chai')
, expect = chai.expect
, assert = chai.assert
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
, Sequelize = require(__dirname + '/../index')
, config = require(__dirname + "/config/config")
, moment = require('moment')
, Transaction = require(__dirname + '/../lib/transaction')
, path = require('path')
, sinon = require('sinon')
chai.Assertion.includeStack = true
......@@ -40,6 +42,38 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
if (dialect !== 'sqlite') {
describe('authenticate', function() {
describe('with valid credentials', function() {
it('triggers the success event', function(done) {
this.sequelize.authenticate().success(done)
})
})
describe('with invalid credentials', function() {
beforeEach(function() {
this.sequelizeWithInvalidCredentials = new Sequelize("omg", "wtf", "lol", this.sequelize.options)
})
it('triggers the error event', function(done) {
this
.sequelizeWithInvalidCredentials
.authenticate()
.complete(function(err, result) {
expect(err).to.not.be.null
done()
})
})
})
})
}
describe('getDialect', function() {
it('returns the defined dialect', function() {
expect(this.sequelize.getDialect()).to.equal(dialect)
})
})
describe('isDefined', function() {
it("returns false if the dao wasn't defined before", function() {
expect(this.sequelize.isDefined('Project')).to.be.false
......@@ -362,28 +396,26 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
it("fails with incorrect database credentials", function(done) {
// sqlite doesn't have a concept of database credentials
if (dialect === "sqlite") {
expect(true).to.be.true
return done()
}
if (dialect !== "sqlite") {
it("fails with incorrect database credentials", function(done) {
this.sequelizeWithInvalidCredentials = new Sequelize("omg", "bar", null, this.sequelize.options)
var sequelize2 = Support.getSequelizeInstance('foo', 'bar', null, { logging: false })
, User2 = sequelize2.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT })
User2.sync().error(function(err) {
if (dialect === "postgres" || dialect === "postgres-native") {
assert([
'role "bar" does not exist',
'password authentication failed for user "bar"'
].indexOf(err.message) !== -1)
} else {
expect(err.message.toString()).to.match(/.*Access\ denied.*/)
}
done()
var User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT })
User2.sync().error(function(err) {
if (dialect === "postgres" || dialect === "postgres-native") {
assert([
'role "bar" does not exist',
'FATAL: role "bar" does not exist',
'password authentication failed for user "bar"'
].indexOf(err.message.trim()) !== -1)
} else {
expect(err.message.toString()).to.match(/.*Access\ denied.*/)
}
done()
})
})
})
}
describe("doesn't emit logging when explicitly saying not to", function() {
afterEach(function(done) {
......@@ -520,5 +552,133 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
describe('transaction', function() {
beforeEach(function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
self.sequelizeWithTransaction = sequelize
done()
})
})
it('is a transaction method available', function() {
expect(Support.Sequelize).to.respondTo('transaction')
})
it('passes a transaction object to the callback', function(done) {
this.sequelizeWithTransaction.transaction(function(t) {
expect(t).to.be.instanceOf(Transaction)
done()
})
})
it('returns a transaction object', function() {
expect(this.sequelizeWithTransaction.transaction(function(){})).to.be.instanceOf(Transaction)
})
it('allows me to define a callback on the result', function(done) {
this
.sequelizeWithTransaction
.transaction(function(t) { t.commit() })
.done(done)
})
it('allows me to define a callback on the transaction object', function(done) {
this.sequelizeWithTransaction.transaction(function(t) {
t.done(done)
t.commit()
})
})
if (dialect === 'sqlite') {
it("correctly scopes transaction from other connections", function(done) {
var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false })
, self = this
var count = function(transaction, callback) {
var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] })
self
.sequelizeWithTransaction
.query(sql, null, { plain: true, raw: true, transaction: transaction })
.success(function(result) { callback(result.cnt) })
}
TransactionTest.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction(function(t1) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { plain: true, raw: true, transaction: t1 }).success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(0)
count(t1, function(cnt) {
expect(cnt).to.equal(1)
t1.commit().success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(1)
done()
})
})
})
})
})
})
})
})
} else {
it("correctly handles multiple transactions", function(done) {
var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false })
, self = this
var count = function(transaction, callback) {
var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] })
self
.sequelizeWithTransaction
.query(sql, null, { plain: true, raw: true, transaction: transaction })
.success(function(result) { callback(parseInt(result.cnt, 10)) })
}
TransactionTest.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction(function(t1) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { plain: true, raw: true, transaction: t1 }).success(function() {
self.sequelizeWithTransaction.transaction(function(t2) {
self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'bar\');', null, { plain: true, raw: true, transaction: t2 }).success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(0)
count(t1, function(cnt) {
expect(cnt).to.equal(1)
count(t2, function(cnt) {
expect(cnt).to.equal(1)
t2.rollback().success(function() {
count(t2, function(cnt) {
expect(cnt).to.equal(0)
t1.commit().success(function() {
count(null, function(cnt) {
expect(cnt).to.equal(1)
})
})
})
})
})
})
})
})
})
})
}).done(function() {
done()
})
})
})
}
})
})
})
var fs = require('fs')
, path = require('path')
, Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types")
, Config = require(__dirname + "/config/config")
......@@ -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) {
options = options || {}
options.dialect = options.dialect || 'mysql'
......@@ -49,6 +63,10 @@ var Support = {
sequelizeOptions.define = options.define
}
if (!!config.storage) {
sequelizeOptions.storage = config.storage
}
if (process.env.DIALECT === 'postgres-native') {
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')
, spies = require('chai-spies')
, expect = chai.expect
, Utils = require(__dirname + '/../lib/utils')
, Support = require(__dirname + '/support')
chai.use(spies)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Utils"), function() {
......@@ -158,4 +160,59 @@ describe(Support.getTestDialectTeaser("Utils"), function() {
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!