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

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
*~
test/binary/tmp/*
test/tmp/*
test/sqlite/test.sqlite
coverage-*
......@@ -13,11 +13,15 @@ teaser:
test:
@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 \
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
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:
@DIALECT=mariadb make test
......@@ -32,6 +36,33 @@ postgres-native:
binary:
@./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
pgsql: postgres
......@@ -41,4 +72,7 @@ postgresn: postgres-native
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
......@@ -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~~
......@@ -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
- ~~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 socket paths. [#982](https://github.com/sequelize/sequelize/issues/982)
### 1.7.x
- Complete support for non-id primary keys
......
......@@ -34,19 +34,22 @@ var writeDefaultConfig = function(config) {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
}
}, undefined, 2) + "\n"
......
......@@ -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 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] 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] 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
......@@ -87,6 +88,8 @@
- [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] 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
......
......@@ -32,8 +32,8 @@ module.exports = (function() {
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes)
// Sync attributes and setters/getters to DAO prototype
this.source.refreshAttributes()
return this
}
......@@ -47,6 +47,7 @@ module.exports = (function() {
obj[accessor] = function(params) {
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')
, Transaction = require('./../transaction')
module.exports = (function() {
var HasManyDoubleLinked = function(definition, instance) {
......@@ -10,7 +11,9 @@ module.exports = (function() {
}
HasManyDoubleLinked.prototype.injectGetter = function(options) {
var self = this, _options = options
var self = this
, _options = options
, smart
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}
......@@ -96,8 +99,14 @@ module.exports = (function() {
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
, obsoleteAssociations = []
, 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)
......@@ -130,15 +139,17 @@ 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)
......@@ -149,12 +160,12 @@ module.exports = (function() {
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))
})
}
......
var Utils = require('./../utils')
, Transaction = require('./../transaction')
module.exports = (function() {
var HasManySingleLinked = function(definition, instance) {
......@@ -9,6 +10,7 @@ module.exports = (function() {
HasManySingleLinked.prototype.injectGetter = function(options) {
var self = this
, where = {}
, smart
options = options || {}
var primaryKey = Object.keys(this.instance.rawAttributes).filter(function(k) { return self.instance.rawAttributes[k].primaryKey === true })
......@@ -28,11 +30,12 @@ module.exports = (function() {
return this.__factory.target.all(options)
}
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
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'
, 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]
......@@ -45,6 +48,11 @@ module.exports = (function() {
})
, 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) {
......@@ -54,12 +62,17 @@ module.exports = (function() {
update = {}
update[self.__factory.identifier] = null
var primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, 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) {
......@@ -80,7 +93,12 @@ module.exports = (function() {
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]}))
chainer.add(this.__factory.target.update(
update,
updateWhere,
Utils._.extend(options, { allowNull: [self.__factory.identifier] })
))
}
chainer
......
......@@ -109,9 +109,9 @@ module.exports = (function() {
Utils._.defaults(this.target.rawAttributes, newAttributes)
}
// Sync attributes to DAO proto each time a new assoc is added
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes);
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes);
// Sync attributes and setters/getters to DAO prototype
this.target.refreshAttributes()
this.source.refreshAttributes()
return this
}
......@@ -124,11 +124,11 @@ 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)})
instance[self.accessors.get](options)
.error(function(err) { customEventEmitter.emit('error', err) })
.success(function(associatedObjects) {
customEventEmitter.emit('success',
Utils._.all(objects, function(o) {
......@@ -144,10 +144,10 @@ module.exports = (function() {
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',
......
......@@ -37,14 +37,14 @@ module.exports = (function() {
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes);
// Sync attributes and setters/getters to DAO prototype
this.target.refreshAttributes()
return this
}
HasOne.prototype.injectGetter = function(obj) {
var self = this
, smart
obj[this.accessors.get] = function(params) {
var primaryKeys = Object.keys(this.daoFactory.primaryKeys)
......@@ -77,7 +77,7 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) {
var self = this
obj[this.accessors.set] = function(associatedObject) {
obj[this.accessors.set] = function(associatedObject, options) {
var instance = this
, instanceKeys = Object.keys(instance.daoFactory.primaryKeys)
, instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id'
......@@ -86,11 +86,18 @@ module.exports = (function() {
instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) {
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) {
associatedObject[self.identifier] = instance[instanceKey]
associatedObject
.save()
.save(options)
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
......@@ -101,7 +108,7 @@ module.exports = (function() {
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 {
......
......@@ -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]
}
......@@ -134,9 +140,10 @@ module.exports = (function() {
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = ['mysql', 'mariadb'].indexOf(self.daoFactory.daoFactoryManager.sequelize.options.dialect) !== -1
, 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
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())
valueOutOfScope = scopeIndex === -1
......@@ -172,7 +179,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 +231,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 +242,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) {
......@@ -270,9 +277,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) {
......@@ -314,7 +326,7 @@ module.exports = (function() {
this.isDirty = isDirty
}
DAO.prototype.destroy = function() {
DAO.prototype.destroy = function(options) {
var self = this
, query = null
......@@ -327,10 +339,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) {
......@@ -352,35 +364,73 @@ module.exports = (function() {
}).run()
}
DAO.prototype.increment = function(fields, count) {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id },
values = {}
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 + " })"
})
if (count === undefined) {
count = 1;
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
, 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)) {
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]) {
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
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) {
......@@ -411,43 +461,6 @@ module.exports = (function() {
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;
}
......
......@@ -188,6 +188,9 @@ module.exports = {
FLOAT: FLOAT,
NOW: 'NOW',
BLOB: BLOB,
UUID: 'CHAR(36)',
UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4',
get ENUM() {
var result = function() {
......
......@@ -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;
......@@ -650,7 +694,7 @@ module.exports = (function() {
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic)
var logicResult = Utils.getWhereLogic(logic, hash[key][logic]);
if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
......
......@@ -427,9 +427,14 @@ module.exports = (function() {
for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || []
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName])
}
}
}
})
return result
......
......@@ -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 = [],
......@@ -32,6 +32,8 @@ module.exports = (function() {
.on('result', function(results) {
results
.on('row', function(row, metadata) {
var type
for (var prop in row) {
if (row.hasOwnProperty(prop)) {
if (row[prop] === null) {
......
......@@ -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':
......
......@@ -244,7 +244,7 @@ module.exports = (function() {
return query
},
incrementQuery: function (tableName, attrValueHash, where) {
incrementQuery: function (tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var values = []
......@@ -256,6 +256,12 @@ module.exports = (function() {
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)
values = values.join(",")
where = this.getWhereConditions(where)
......@@ -327,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")
......@@ -60,7 +61,9 @@ module.exports = (function() {
var query = new Query(self.client, self.sequelize, callee, options || {})
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) })
.error(function(err) { self.endQuery.call(self) })
.proxy(emitter)
......@@ -68,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)()
......@@ -106,12 +113,25 @@ 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.setTimezone(self.client, 'UTC', function() {
self.isConnected = true
emitter.emit('success', done)
})
} else {
done && done()
......@@ -129,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()
......
......@@ -145,10 +145,14 @@ module.exports = (function() {
},
arrayValue: function(value, key, _key, factory){
var col = null
, coltype = null
, _realKey = key.split('.').pop()
, _value
if (value.length === 0) { value = [null] }
var col = null, coltype = null
// Special conditions for searching within an array column type
var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey]
coltype = col.type
......@@ -366,7 +370,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
incrementQuery: function(tableName, attrValueHash, where) {
incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
......@@ -377,6 +381,12 @@ module.exports = (function() {
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 = {
table: this.quoteIdentifiers(tableName),
values: values.join(","),
......@@ -870,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
......
......@@ -27,7 +27,7 @@ module.exports = (function() {
, 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) {
......@@ -57,7 +57,7 @@ module.exports = (function() {
}
var onSuccess = function(rows, sql) {
var results = []
var results = rows
, self = this
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
......
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")),
......@@ -81,8 +82,15 @@ module.exports = (function() {
var length = dataType.match(/\(\s*\d+(\s*,\s*\d)?\s*\)/)
if (length && length.index < modifierLastIndex) {
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) {
......@@ -214,7 +222,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
incrementQuery: function(tableName, attrValueHash, where) {
incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
......@@ -225,6 +233,12 @@ module.exports = (function() {
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 = {
table: this.quoteIdentifier(tableName),
values: values.join(","),
......@@ -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) {
return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1")
},
......
......@@ -26,19 +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() {
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)
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)) {
var tableName = RegExp.$1;
......
var util = require("util")
, EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, 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)
}
......
......@@ -233,7 +233,7 @@ module.exports = (function() {
// private
var getLastMigrationFromDatabase = function() {
var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
......@@ -255,7 +255,7 @@ module.exports = (function() {
}).run()
}
var getLastMigrationIdFromDatabase = function() {
var getLastMigrationIdFromDatabase = Migrator.prototype.getLastMigrationIdFromDatabase = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
......@@ -270,7 +270,7 @@ module.exports = (function() {
}).run()
}
var getFormattedDateString = function(s) {
var getFormattedDateString = Migrator.prototype.getFormattedDateString = function(s) {
var result = null
try {
......@@ -282,11 +282,11 @@ module.exports = (function() {
return result
}
var stringToDate = function(s) {
var stringToDate = Migrator.prototype.stringToDate = function(s) {
return moment(getFormattedDateString(s), "YYYYMMDDHHmmss")
}
var saveSuccessfulMigration = function(from, to, callback) {
var saveSuccessfulMigration = Migrator.prototype.saveSuccessfulMigration = function(from, to, callback) {
var self = this
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
......@@ -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
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
......
......@@ -75,6 +75,10 @@ module.exports = (function() {
if (options.skipOnError && (self.fails.length > 0)) {
onError('Skipped due to earlier error!')
} 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)
emitter.success(function(result) {
......
......@@ -5,6 +5,8 @@ var url = require("url")
, 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: {},
......@@ -109,14 +112,8 @@ module.exports = (function() {
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.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
......@@ -288,7 +303,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) {
......@@ -330,6 +345,8 @@ module.exports = (function() {
options = Utils._.extend({}, this.options.sync, options)
}
options.logging = options.logging === undefined ? false : Boolean(options.logging)
var chainer = new Utils.QueryChainer()
// Topologically sort by foreign key constraints to give us an appropriate
......@@ -357,6 +374,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))
}
......@@ -373,5 +406,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
})()
......@@ -143,7 +143,7 @@ SqlString.format = 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)) {
return SqlString.escape(values[key], false, timeZone, dialect)
} 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")
, 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() {
......@@ -180,7 +182,7 @@ var Utils = module.exports = {
}
else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii)
logic = self.getWhereLogic(ii, where[i][ii]);
switch(logic) {
case 'IN':
......@@ -282,7 +284,7 @@ var Utils = module.exports = {
return Utils._.compactLite([text.join(' AND ')].concat(whereArgs))
},
getWhereLogic: function(logic) {
getWhereLogic: function(logic, val) {
switch (logic) {
case 'join':
return 'JOIN'
......@@ -297,7 +299,7 @@ var Utils = module.exports = {
case 'eq':
return '='
case 'ne':
return '!='
return val ? '!=' : 'IS NOT'
case 'between':
case '..':
return 'BETWEEN'
......@@ -375,8 +377,14 @@ var Utils = module.exports = {
toDefaultValue: function(value) {
if (lodash.isFunction(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 {
return (value === DataTypes.NOW) ? Utils.now() : value
return value
}
},
......@@ -394,6 +402,8 @@ var Utils = module.exports = {
// have been normalized for this case
if (value === DataTypes.NOW) {return false}
if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) {return false}
if (lodash.isFunction(value)) {
return false
}
......@@ -488,13 +498,17 @@ var Utils = module.exports = {
return stack;
},
now: function(dialect) {
var now = new Date()
if(dialect != "postgres") now.setMilliseconds(0)
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.
......@@ -524,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,25 +40,31 @@
"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",
"promise": "~3.2.0",
"sql": "~0.28.0"
"sql": "~0.31.0",
"circular-json": "~0.1.5",
"bluebird": "~0.11.5",
"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"
},
"keywords": [
"mysql",
......
......@@ -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 })
......
......@@ -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) {
......@@ -409,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User)
expect(Task.attributes.UserId).not.to.exist
setTimeout(function () {
done()
}, 50)
})
describe('setAssociations', function() {
......
......@@ -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'})
......
......@@ -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: {
......
......@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.UUID, 'UUID', 'CHAR(36)'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'],
......
......@@ -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
......@@ -6087,6 +6087,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
})
it('on success', function(done) {
var beforeHook
, afterHook
this.User.beforeDelete(function(user, fn) {
beforeHook = true
fn()
......@@ -6107,6 +6110,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
})
it('on error', function(done) {
var beforeHook
, afterHook
this.User.beforeDelete(function(user, fn) {
beforeHook = true
fn()
......@@ -6142,6 +6148,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
})
it('on success', function(done) {
var beforeHook
, afterHook
this.User.hook('beforeDelete', function(user, fn) {
beforeHook = true
fn()
......@@ -6162,6 +6171,9 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
})
it('on error', function(done) {
var beforeHook
, afterHook
this.User.hook('beforeDelete', function(user, fn) {
beforeHook = true
fn()
......
......@@ -30,7 +30,10 @@ if (dialect.match(/^postgres/)) {
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
setTimeout(function () {
done()
}, 50)
})
it("should not use a combined name", function(done) {
......
......@@ -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)
})
})
})
......
......@@ -91,8 +91,8 @@ if (dialect === 'sqlite') {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
},
{
arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5));"
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) PRIMARY KEY);"
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
......
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!