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

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
}
......@@ -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) {
......@@ -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 = {}
......@@ -46,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)
)
})
......@@ -88,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)
......@@ -119,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)
}
})
......@@ -130,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))
})
}
......@@ -189,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) {
......@@ -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,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) {
......@@ -54,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
......@@ -77,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
......
......@@ -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,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',
......
......@@ -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,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
......@@ -141,28 +147,8 @@ module.exports = (function() {
})
}
Utils._.each(['Get', 'Set'], function(type) {
var prop = type.toLowerCase()
, opt = prop + 'terMethods'
, meth = '__define' + type + 'ter__'
, funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
this.refreshAttributes();
Utils._.each(self.rawAttributes, function(attr, name) {
if (attr.hasOwnProperty(prop)) {
funcs[name] = attr[prop]
}
})
Utils._.each(funcs, function(fct, name) {
if (!Utils._.isFunction(fct)) {
throw new Error(type + 'ter for "' + name + '" is not a function.')
}
self.DAO.prototype[meth](name, fct)
})
})
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = []
this.DAO.prototype.defaultValues = {}
this.DAO.prototype.validators = {}
......@@ -187,13 +173,62 @@ module.exports = (function() {
return this
}
DAOFactory.prototype.refreshAttributes = function() {
var self = this
, attributeManipulation = {};
Utils._.each(['get', 'set'], function(type) {
var opt = type + 'terMethods'
, funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
Utils._.each(self.rawAttributes, function(options, attribute) {
if (options.hasOwnProperty(type)) {
funcs[attribute] = options[type]
} else if (typeof funcs[attribute] === "undefined") {
if (type === 'get') {
funcs[attribute] = function() { return this.dataValues[attribute]; }
}
if (type === 'set') {
funcs[attribute] = function(value) {
if (Utils.hasChanged(this.dataValues[attribute], value)) {
//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] = value
}
}
}
})
Utils._.each(funcs, function(fct, name) {
if (!attributeManipulation[name]) {
attributeManipulation[name] = {
configurable: true
}
}
attributeManipulation[name][type] = fct
})
})
Object.defineProperties(this.DAO.prototype, attributeManipulation)
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes)
}
DAOFactory.prototype.sync = function(options) {
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) })
......@@ -201,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()
}
......@@ -212,6 +250,7 @@ module.exports = (function() {
// Only Postgres' QueryGenerator.dropTableQuery() will add schema manually
var isPostgres = this.options.dialect === "postgres" || (!!this.daoFactoryManager && this.daoFactoryManager.sequelize.options.dialect === "postgres")
, tableName = isPostgres ? this.tableName : this.getTableName()
return this.QueryInterface.dropTable(tableName, options)
}
......@@ -247,6 +286,7 @@ module.exports = (function() {
, i
, scope
, scopeName
, scopeOptions
, argLength = arguments.length
, lastArg = arguments[argLength-1]
......@@ -347,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
......@@ -360,7 +400,7 @@ module.exports = (function() {
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
}, queryOptions))
}, queryOptions, { transaction: (options || {}).transaction }))
}
/**
......@@ -526,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
......@@ -563,19 +622,31 @@ module.exports = (function() {
}).run()
}
DAOFactory.prototype.findOrCreate = function (params, defaults) {
var self = this;
DAOFactory.prototype.findOrCreate = function (where, defaults, options) {
var self = this
, params = {}
options = Utils._.extend({
transaction: null
}, options || {})
for (var attrname in where) {
params[attrname] = where[attrname]
}
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)
})
......@@ -605,32 +676,39 @@ module.exports = (function() {
* 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' })
fields = fields || []
options = Utils._.extend({
validate: false,
hooks: false
}, options || {})
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)
})
}
......@@ -651,7 +729,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) {
......@@ -679,9 +757,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]
})
......@@ -698,7 +776,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)
})
......@@ -709,18 +787,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}).complete(function (err) {
daos[i].hookValidate({skip: options.fields}).complete(function (err) {
if (!!err) {
errors.push({record: v, errors: err})
}
......@@ -734,12 +812,12 @@ module.exports = (function() {
})
}
} else {
var afterDaos = Utils._.after(daos.length, function() {
var afterDaos = Utils._.after(daos.length, function() {
next(errors.length > 0 ? errors : null)
})
daos.forEach(function(v) {
v.validate({skip: fields}).success(function(err) {
v.validate({skip: options.fields}).success(function(err) {
if (!!err) {
errors.push({record: v, errors: err})
}
......@@ -1029,7 +1107,7 @@ module.exports = (function() {
}
DAOFactory.prototype.__setSqlDialect = function() {
var dialect = this.daoFactoryManager.sequelize.options.dialect
var dialect = this.daoFactoryManager.sequelize.options.dialect
this.__sql = sql.setDialect(dialect === 'mariadb' ? 'mysql' : dialect)
}
......
......@@ -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
}
return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier)
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, 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
})
}
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,7 +427,12 @@ module.exports = (function() {
for (var attrName in row) {
if (row.hasOwnProperty(attrName) && (attrName !== calleeTableName)) {
existingEntry[attrName] = existingEntry[attrName] || []
existingEntry[attrName].push(row[attrName])
var attrRowExists = existingEntry[attrName].some(function(attrRow) {
return Utils._.isEqual(attrRow, row[attrName])
})
if (!attrRowExists) {
existingEntry[attrName].push(row[attrName])
}
}
}
})
......
......@@ -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) {
......@@ -65,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":
......@@ -102,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':
......
......@@ -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()
......@@ -153,4 +179,4 @@ module.exports = (function() {
}
return ConnectorManager
})()
\ No newline at end of file
})()
......@@ -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
......
......@@ -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) {
......@@ -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)
......@@ -162,4 +162,4 @@ module.exports = (function() {
}
return Query
})()
\ No newline at end of file
})()
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")),
......@@ -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() {
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 (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;
......
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) {
......
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) {
......@@ -79,6 +80,10 @@ module.exports = (function() {
}
}
options = Utils._.extend({
logging: this.sequelize.options.logging
}, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") {
......@@ -90,7 +95,7 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/)) {
sql = self.QueryGenerator.pgListEnums(getTableName, keys[i], options)
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT' }))
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT', logging: options.logging }))
}
}
......@@ -107,9 +112,8 @@ module.exports = (function() {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options)
chainer2.add(self.sequelize.query(sql, null, { raw: true }))
}
else if (!!results[enumIdx] && !!daoTable) {
chainer2.add(self.sequelize.query(sql, null, { raw: true, logging: options.logging }))
} else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
......@@ -137,16 +141,19 @@ module.exports = (function() {
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
chainer2.run().success(function() {
queryAndEmit.call(self, sql, 'createTable')
.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) {
......@@ -157,7 +164,7 @@ module.exports = (function() {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
queryAndEmit.call(self, sql, 'createTable', emitter).success(function(results) {
queryAndEmit.call(self, sql, 'createTable', options).success(function(results) {
self.emit('createTable', null)
emitter.emit('success', results)
}).error(function(err) {
......@@ -181,7 +188,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql])
chainer.add(self, 'queryAndEmit', [sql, 'dropTable'], options)
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
......@@ -202,7 +209,7 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) {
if (daoTable.rawAttributes[keys[i]].type && daoTable.rawAttributes[keys[i]].type === "ENUM") {
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {raw: true}])
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {logging: options.logging, raw: true}])
}
}
}
......@@ -443,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) {
......@@ -475,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){
......@@ -501,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){
......@@ -517,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 = []
......@@ -586,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){
......@@ -616,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){
......@@ -656,9 +663,9 @@ module.exports = (function() {
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
}
QueryInterface.prototype.increment = function(dao, tableName, values, identifier) {
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier);
return queryAndEmit.call(this, [sql, dao], 'increment');
QueryInterface.prototype.increment = function(dao, tableName, values, identifier, options) {
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) {
......@@ -669,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
......@@ -798,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() {
......@@ -811,8 +876,10 @@ module.exports = (function() {
var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
options = Utils._.extend({
success: function(){},
error: function(){}
success: function(){},
error: function(){},
transaction: null,
logging: this.sequelize.options.logging
}, options || {})
var execQuery = function(emitter) {
......@@ -824,30 +891,29 @@ module.exports = (function() {
}
if (sqlOrQueryParams.length === 2) {
sqlOrQueryParams.push({})
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, {})
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
......@@ -250,7 +265,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)
}
......@@ -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) })
}
var util = require("util")
, DataTypes = require("./data-types")
, SqlString = require("./sql-string")
, lodash = require("lodash")
, _string = require('underscore.string')
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() {
......@@ -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",
......
......@@ -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) {
......@@ -409,7 +535,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Task.hasMany(User)
expect(Task.attributes.UserId).not.to.exist
done()
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'})
......@@ -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: {
......
......@@ -25,6 +25,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
})
this.User.sync({ force: true }).success(function() {
done()
})
......@@ -164,7 +165,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var UserTable = this.sequelize.define('UserCol', {
aNumber: {
type: Sequelize.INTEGER,
defaultValue: defaultFunction
defaultValue: defaultFunction
}
}, { timestamps: true })
......@@ -351,6 +352,29 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('findOrInitialize', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findOrInitialize({ username: 'foo' }).success(function(user1) {
User.findOrInitialize({ username: 'foo' }, { transaction: t }).success(function(user2) {
User.findOrInitialize({ username: 'foo' }, { foo: 'asd' }, { transaction: t }).success(function(user3) {
expect(user1.isNewRecord).to.be.true
expect(user2.isNewRecord).to.be.false
expect(user3.isNewRecord).to.be.false
t.commit().success(function() { done() })
})
})
})
})
})
})
})
})
describe('returns an instance if it already exists', function() {
it('with a single find field', function (done) {
var self = this
......@@ -406,7 +430,35 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('findOrCreate', function () {
it("Returns instance if already existent. Single find field.", function(done) {
it("supports transactions", function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING, data: Sequelize.STRING })
User
.sync({ force: true })
.success(function() {
sequelize.transaction(function(t) {
User.findOrCreate({ username: 'Username' }, { data: 'some data' }, { transaction: t }).complete(function(err) {
expect(err).to.be.null
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
})
})
})
it("returns instance if already existent. Single find field.", function(done) {
var self = this,
data = {
username: 'Username'
......@@ -460,6 +512,30 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('create', function() {
it('supports transactions', function(done) {
var self = this
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('user_with_transaction', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'user' }, { transaction: t }).success(function() {
User.count().success(function(count) {
expect(count).to.equal(0)
t.commit().success(function() {
User.count().success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
})
})
})
it('is possible to use casting when creating an instance', function (done) {
var self = this
, type = Support.dialectIsMySQL() ? 'signed' : 'integer'
......@@ -620,6 +696,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres") {
expect('').to.equal('')
......@@ -644,7 +721,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true }
......@@ -798,7 +874,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data, ['username']).success(function(user) {
this.User.create(data, { fields: ['username'] }).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).to.equal(data.username)
expect(_user.secretValue).not.to.equal(data.secretValue)
......@@ -949,10 +1025,67 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
describe('can safely sync multiple times', function(done) {
it('through the factory', function(done) {
var Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
Enum.sync({ force: true }).success(function() {
Enum.sync().success(function() {
Enum.sync({ force: true }).complete(done)
})
})
})
it('through sequelize', function(done) {
var self = this
, Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
this.sequelize.sync({ force: true }).success(function() {
self.sequelize.sync().success(function() {
self.sequelize.sync({ force: true }).complete(done)
})
})
})
})
})
})
describe('bulkCreate', function() {
it("supports transactions", function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User
.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction: t })
.success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(0)
expect(count2).to.equal(2)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it('properly handles disparate field lists', function(done) {
var self = this
, data = [{username: 'Peter', secretValue: '42' },
......@@ -974,7 +1107,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data, ['username']).success(function() {
this.User.bulkCreate(data, { fields: ['username'] }).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
......@@ -1095,7 +1228,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
{name: 'foo', code: '123'},
{code: '1234'},
{name: 'bar', code: '1'}
], null, {validate: true}).error(function(errors) {
], { validate: true }).error(function(errors) {
expect(errors).to.not.be.null
expect(errors).to.be.instanceof(Array)
expect(errors).to.have.length(2)
......@@ -1129,7 +1262,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
Tasks.bulkCreate([
{name: 'foo', code: '123'},
{code: '1234'}
], ['code'], {validate: true}).success(function() {
], { fields: ['code'], validate: true }).success(function() {
// we passed!
done()
})
......@@ -1157,6 +1290,28 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('update', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) {
User.update({ username: 'bar' }, {}, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1[0].username).to.equal('foo')
expect(users2[0].username).to.equal('bar')
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
})
it('updates the attributes that we select only without updating createdAt', function(done) {
var User = this.sequelize.define('User1', {
username: Sequelize.STRING,
......@@ -1279,20 +1434,21 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('destroy', function() {
it('deletes a record from the database if dao is not paranoid', function(done) {
var UserDestroy = this.sequelize.define('UserDestroy', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
UserDestroy.sync({ force: true }).success(function() {
UserDestroy.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().success(function() {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(0)
done()
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function() {
sequelize.transaction(function(t) {
User.destroy({}, { transaction: t }).success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(1)
expect(count2).to.equal(0)
t.rollback().success(function(){ done() })
})
})
})
})
})
......@@ -1300,26 +1456,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('allows sql logging of delete statements', function(done) {
var UserDelete = this.sequelize.define('UserDelete', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
UserDelete.sync({ force: true }).success(function() {
UserDelete.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDelete.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("DELETE")).to.be.above(-1)
done()
})
})
})
})
})
it('deletes values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
......@@ -1414,158 +1550,21 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
describe('special where conditions/smartWhere object', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'boo', intVal: 5, theDate: '2013-01-01 12:00'},
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00'}
]).success(function(user2) {
done()
})
})
it('should be able to find rows where attribute is in a list of values', function (done) {
this.User.findAll({
where: {
username: ['boo', 'boo2']
}
}).success(function(users){
expect(users).to.have.length(2);
done()
});
})
it('should not break when trying to find rows using an array of primary keys', function (done) {
this.User.findAll({
where: {
id: [1, 2, 3]
}
}).success(function(users){
done();
});
})
it('should be able to find a row using like', function(done) {
this.User.findAll({
where: {
username: {
like: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using not like', function(done) {
this.User.findAll({
where: {
username: {
nlike: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row between a certain date using the between shortcut', function(done) {
this.User.findAll({
where: {
theDate: {
'..': ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer using the not between shortcut', function(done) {
this.User.findAll({
where: {
intVal: {
'!..': [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to handle false/true values just fine...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', false]}).success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo5')
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', true]}).success(function(_users) {
expect(_users).to.have.length(1)
expect(_users[0].username).to.equal('boo6')
done()
})
})
})
})
it('should be able to handle false/true values through associations as well...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
var Passports = this.sequelize.define('Passports', {
isActive: Sequelize.BOOLEAN
})
User.hasMany(Passports)
Passports.belongsTo(User)
describe('find', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
Passports.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
Passports.bulkCreate([
{isActive: true},
{isActive: false}
]).success(function() {
User.find(1).success(function(user) {
Passports.find(1).success(function(passport) {
user.setPassports([passport]).success(function() {
User.find(2).success(function(_user) {
Passports.find(2).success(function(_passport) {
_user.setPassports([_passport]).success(function() {
_user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', false]}).success(function(theFalsePassport) {
user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', true]}).success(function(theTruePassport) {
expect(theFalsePassport).to.have.length(1)
expect(theFalsePassport[0].isActive).to.be.false
expect(theTruePassport).to.have.length(1)
expect(theTruePassport[0].isActive).to.be.true
done()
})
})
})
})
})
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.find({ username: 'foo' }).success(function(user1) {
User.find({ username: 'foo' }, { transaction: t }).success(function(user2) {
expect(user1).to.be.null
expect(user2).to.not.be.null
t.rollback().success(function() {
done()
})
})
})
......@@ -1575,235 +1574,6 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should be able to return a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
lastUpdate: { type: DataTypes.DATE, allowNull: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci',
omitNull: true
})
, User = this.sequelize.define('User', {
name: { type: DataTypes.STRING, allowNull: false, unique: true },
password: { type: DataTypes.STRING, allowNull: false },
isAdmin: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci'
})
User.hasMany(Session, { as: 'Sessions' })
Session.belongsTo(User)
Session.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
User.create({name: 'Name1', password: '123', isAdmin: false}).success(function(user) {
var sess = Session.build({
lastUpdate: new Date(),
token: '123'
})
user.addSession(sess).success(function(u) {
expect(u.token).to.equal('123')
done()
})
})
})
})
})
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row between a certain date and an additional where clause', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
},
intVal: 10
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer', function(done) {
this.User.findAll({
where: {
intVal: {
nbetween: [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2012-12-10', '2013-01-02'],
nbetween: ['2013-01-04', '2013-01-20']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
between: [new Date('2012-12-10'), new Date('2013-01-02')],
nbetween: [new Date('2013-01-04'), new Date('2013-01-20')]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using greater than or equal to logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
gte: new Date('2013-01-09')
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than or equal to', function(done) {
this.User.find({
where: {
intVal: {
gte: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than', function(done) {
this.User.find({
where: {
intVal: {
gt: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using lesser than or equal to', function(done) {
this.User.find({
where: {
intVal: {
lte: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find a row using lesser than', function(done) {
this.User.find({
where: {
intVal: {
lt: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should have no problem finding a row using lesser and greater than', function(done) {
this.User.findAll({
where: {
intVal: {
lt: 6,
gt: 4
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not equal to logic', function(done) {
this.User.find({
where: {
intVal: {
ne: 10
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find multiple users with any of the special where logic properties', function(done) {
this.User.findAll({
where: {
intVal: {
lte: 10
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
expect(users[1].username).to.equal('boo2')
expect(users[1].intVal).to.equal(10)
done()
})
})
})
describe('find', function() {
describe('general / basic function', function() {
beforeEach(function(done) {
var self = this
......@@ -1918,7 +1688,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
this.User.find({ limit: 10 }).success(function(user) {
// it returns an object instead of an array
expect(Array.isArray(user)).to.not.be.ok
expect(user.hasOwnProperty('username')).to.be.ok
expect(user.dataValues.hasOwnProperty('username')).to.be.ok
done()
})
})
......@@ -2471,6 +2241,45 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('including two has many relations should not result in duplicate values', function(done) {
var self = this
self.Contact = self.sequelize.define('Contact', { name: DataTypes.TEXT })
self.Photo = self.sequelize.define('Photo', { img: DataTypes.TEXT })
self.PhoneNumber = self.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT })
self.Contact.hasMany(self.Photo, { as: 'Photos' })
self.Contact.hasMany(self.PhoneNumber)
self.sequelize.sync({ force: true }).success(function() {
self.Contact.create({ name: 'Boris' }).success(function(someContact) {
self.Photo.create({ img: 'img.jpg' }).success(function(somePhoto) {
self.PhoneNumber.create({ phone: '000000' }).success(function(somePhone1) {
self.PhoneNumber.create({ phone: '111111' }).success(function(somePhone2) {
someContact.setPhotos([somePhoto]).complete(function (err, data) {
expect(err).to.be.null
someContact.setPhoneNumbers([somePhone1, somePhone2]).complete(function (err, data) {
self.Contact.find({
where: {
name: 'Boris'
},
include: [self.PhoneNumber, { daoFactory: self.Photo, as: 'Photos' }]
}).complete(function (err, fetchedContact) {
expect(err).to.be.null
expect(fetchedContact).to.exist
expect(fetchedContact.photos.length).to.equal(1)
expect(fetchedContact.phoneNumbers.length).to.equal(2)
done()
})
})
})
})
})
})
})
})
})
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
......@@ -2488,138 +2297,555 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
self.Group.hasMany(self.User)
self.User.hasMany(self.Group)
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone' }).success(function(someUser) {
self.Group.create({ name: 'people' }).success(function(someGroup) {
someUser.setGroupPKeagerones([someGroup]).complete(function (err, data) {
expect(err).to.be.null
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerones[0].name).to.equal('people')
done()
})
})
})
self.sequelize.sync({ force: true }).success(function() {
self.User.create({ username: 'someone' }).success(function(someUser) {
self.Group.create({ name: 'people' }).success(function(someGroup) {
someUser.setGroupPKeagerones([someGroup]).complete(function (err, data) {
expect(err).to.be.null
self.User.find({
where: {
username: 'someone'
},
include: [self.Group]
}).complete(function (err, someUser) {
expect(err).to.be.null
expect(someUser).to.exist
expect(someUser.username).to.equal('someone')
expect(someUser.groupPKeagerones[0].name).to.equal('people')
done()
})
})
})
})
})
})
})
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
describe('alias', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.init(function() {
self.worker.setToDos([ self.task ]).success(function() {
done()
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDos).to.exist
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'DoTos' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDos' },
{ model: self.Task, as: 'DoTos' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(user).to.be.instanceOf(Object)
done()
})
})
})
})
describe('findAll', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findAll({ username: 'foo' }).success(function(users1) {
User.findAll({ transaction: t }).success(function(users2) {
User.findAll({ username: 'foo' }, { transaction: t }).success(function(users3) {
expect(users1.length).to.equal(0)
expect(users2.length).to.equal(1)
expect(users3.length).to.equal(1)
t.rollback().success(function() {
done()
})
})
})
})
})
})
})
})
})
describe('special where conditions/smartWhere object', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'boo', intVal: 5, theDate: '2013-01-01 12:00'},
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00'}
]).success(function(user2) {
done()
})
})
it('should be able to find rows where attribute is in a list of values', function (done) {
this.User.findAll({
where: {
username: ['boo', 'boo2']
}
}).success(function(users){
expect(users).to.have.length(2);
done()
});
})
it('should not break when trying to find rows using an array of primary keys', function (done) {
this.User.findAll({
where: {
id: [1, 2, 3]
}
}).success(function(users){
done();
});
})
it('should be able to find a row using like', function(done) {
this.User.findAll({
where: {
username: {
like: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using not like', function(done) {
this.User.findAll({
where: {
username: {
nlike: '%2'
}
}
}).success(function(users) {
expect(users).to.be.an.instanceof(Array)
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row between a certain date using the between shortcut', function(done) {
this.User.findAll({
where: {
theDate: {
'..': ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer using the not between shortcut', function(done) {
this.User.findAll({
where: {
intVal: {
'!..': [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to handle false/true values just fine...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', false]}).success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal('boo5')
User.all({where: [escapeChar + 'aBool' + escapeChar + ' = ?', true]}).success(function(_users) {
expect(_users).to.have.length(1)
expect(_users[0].username).to.equal('boo6')
done()
})
})
})
})
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
it('should be able to handle false/true values through associations as well...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres") ? '"' : '`'
var Passports = this.sequelize.define('Passports', {
isActive: Sequelize.BOOLEAN
})
describe('alias', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.init(function() {
self.worker.setToDos([ self.task ]).success(function() {
done()
User.hasMany(Passports)
Passports.belongsTo(User)
User.sync({ force: true }).success(function() {
Passports.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
Passports.bulkCreate([
{isActive: true},
{isActive: false}
]).success(function() {
User.find(1).success(function(user) {
Passports.find(1).success(function(passport) {
user.setPassports([passport]).success(function() {
User.find(2).success(function(_user) {
Passports.find(2).success(function(_passport) {
_user.setPassports([_passport]).success(function() {
_user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', false]}).success(function(theFalsePassport) {
user.getPassports({where: [escapeChar + 'isActive' + escapeChar + ' = ?', true]}).success(function(theTruePassport) {
expect(theFalsePassport).to.have.length(1)
expect(theFalsePassport[0].isActive).to.be.false
expect(theTruePassport).to.have.length(1)
expect(theTruePassport[0].isActive).to.be.true
done()
})
})
})
})
})
})
})
})
})
})
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDos).to.exist
expect(worker.toDos[0].title).to.equal('homework')
done()
it('should be able to return a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
lastUpdate: { type: DataTypes.DATE, allowNull: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci',
omitNull: true
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(worker.toDos[0].title).to.equal('homework')
done()
, User = this.sequelize.define('User', {
name: { type: DataTypes.STRING, allowNull: false, unique: true },
password: { type: DataTypes.STRING, allowNull: false },
isAdmin: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci'
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
User.hasMany(Session, { as: 'Sessions' })
Session.belongsTo(User)
this.Worker.hasMany(this.Task, { as: 'DoTos' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDos' },
{ model: self.Task, as: 'DoTos' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
Session.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
User.create({name: 'Name1', password: '123', isAdmin: false}).success(function(user) {
var sess = Session.build({
lastUpdate: new Date(),
token: '123'
})
user.addSession(sess).success(function(u) {
expect(u.token).to.equal('123')
done()
})
})
})
})
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
it('should be able to find a row between a certain date and an additional where clause', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
},
intVal: 10
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
it('should be able to find a row not between a certain integer', function(done) {
this.User.findAll({
where: {
intVal: {
nbetween: [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(user).to.be.instanceOf(Object)
it('should be able to find a row using not between and between logic', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2012-12-10', '2013-01-02'],
nbetween: ['2013-01-04', '2013-01-20']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
between: [new Date('2012-12-10'), new Date('2013-01-02')],
nbetween: [new Date('2013-01-04'), new Date('2013-01-20')]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using greater than or equal to logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
gte: new Date('2013-01-09')
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than or equal to', function(done) {
this.User.find({
where: {
intVal: {
gte: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than', function(done) {
this.User.find({
where: {
intVal: {
gt: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using lesser than or equal to', function(done) {
this.User.find({
where: {
intVal: {
lte: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find a row using lesser than', function(done) {
this.User.find({
where: {
intVal: {
lt: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should have no problem finding a row using lesser and greater than', function(done) {
this.User.findAll({
where: {
intVal: {
lt: 6,
gt: 4
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not equal to logic', function(done) {
this.User.find({
where: {
intVal: {
ne: 10
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find multiple users with any of the special where logic properties', function(done) {
this.User.findAll({
where: {
intVal: {
lte: 10
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
expect(users[1].username).to.equal('boo2')
expect(users[1].intVal).to.equal(10)
done()
})
})
})
})
describe('findAll', function() {
describe('eager loading', function() {
describe('belongsTo', function() {
beforeEach(function(done) {
......@@ -3053,6 +3279,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.findAndCountAll().success(function(info1) {
User.findAndCountAll({ transaction: t }).success(function(info2) {
expect(info1.count).to.equal(0)
expect(info2.count).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("handles where clause [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id}).success(function(info) {
expect(info.count).to.equal(2)
......@@ -3120,6 +3367,26 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1.length).to.equal(0)
expect(users2.length).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("should return all users", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
......@@ -3189,6 +3456,26 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('count', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(0)
expect(count2).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it('counts all created objects', function(done) {
var self = this
this.User.bulkCreate([{username: 'user1'}, {username: 'user2'}]).success(function() {
......@@ -3247,6 +3534,26 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { age: Sequelize.INTEGER })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).success(function() {
User.min('age').success(function(min1) {
User.min('age', { transaction: t }).success(function(min2) {
expect(min1).to.be.not.ok
expect(min2).to.equal(2)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("should return the min value", function(done) {
var self = this
this.UserWithAge.bulkCreate([{age: 3}, { age: 2 }]).success(function() {
......@@ -3295,6 +3602,26 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { age: Sequelize.INTEGER })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).success(function() {
User.max('age').success(function(min1) {
User.max('age', { transaction: t }).success(function(min2) {
expect(min1).to.be.not.ok
expect(min2).to.equal(5)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it("should return the max value for a field named the same as an SQL reserved keyword", function(done) {
var self = this
this.UserWithAge.bulkCreate([{age: 2, order: 3}, {age: 3, order: 5}]).success(function(){
......@@ -3987,16 +4314,33 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
this
.User
.create({ username: "foo" })
.then(function() {
return self.User.create({ username: "bar" })
})
.then(function() {
return self.User.create({ username: "baz" })
})
.sync({ force: true })
.then(function() { return self.User.create({ username: "foo" }) })
.then(function() { return self.User.create({ username: "bar" }) })
.then(function() { return self.User.create({ username: "baz" }) })
.then(function() { done() })
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.create({ username: 'foo' }, { transaction: t }).success(function() {
User.where({ username: "foo" }).exec().success(function(users1) {
User.where({ username: "foo" }).exec({ transaction: t }).success(function(users2) {
expect(users1).to.have.length(0)
expect(users2).to.have.length(1)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
it("selects all users with name 'foo'", function(done) {
this
.User
......
......@@ -7,6 +7,7 @@ var chai = require('chai')
, config = require(__dirname + "/config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, uuid = require('node-uuid')
, _ = require('lodash')
chai.use(datetime)
......@@ -16,6 +17,8 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: { type: DataTypes.STRING },
uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 },
uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
......@@ -266,10 +269,32 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { number: Support.Sequelize.INTEGER })
User.sync({ force: true }).success(function() {
User.create({ number: 1 }).success(function(user) {
sequelize.transaction(function(t) {
user.increment('number', { by: 2, transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1[0].number).to.equal(1)
expect(users2[0].number).to.equal(3)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
it('with array', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment(['aNumber'], 2).complete(function() {
user1.increment(['aNumber'], { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(2)
done()
......@@ -281,7 +306,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it('with single field', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment('aNumber', 2).complete(function() {
user1.increment('aNumber', { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(2)
done()
......@@ -290,6 +315,18 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('with single field and no value', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment('aNumber').complete(function() {
self.User.find(1).complete(function(err, user2) {
expect(user2.aNumber).to.be.equal(1)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function (err, user1) {
......@@ -298,7 +335,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).complete(function () {
user1.increment(['aNumber'], 2).complete(function() {
user1.increment(['aNumber'], { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user5) {
expect(user5.aNumber).to.be.equal(3)
done()
......@@ -319,16 +356,16 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
user1.increment(['aNumber'], 2).complete(_done)
user1.increment(['aNumber'], 2).complete(_done)
user1.increment(['aNumber'], 2).complete(_done)
user1.increment(['aNumber'], { by: 2 }).complete(_done)
user1.increment(['aNumber'], { by: 2 }).complete(_done)
user1.increment(['aNumber'], { by: 2 }).complete(_done)
})
})
it('with key value pair', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment({ 'aNumber': 1, 'bNumber': 2}).success(function() {
user1.increment({ 'aNumber': 1, 'bNumber': 2 }).success(function() {
self.User.find(1).complete(function (err, user3) {
expect(user3.aNumber).to.be.equal(1)
expect(user3.bNumber).to.be.equal(2)
......@@ -337,6 +374,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it('with timestamps set to true', function (done) {
var User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true })
User.sync({ force: true }).success(function() {
User.create({aNumber: 1}).success(function (user) {
var oldDate = user.updatedAt
setTimeout(function () {
user.increment('aNumber', { by: 1 }).success(function() {
User.find(1).success(function (user) {
expect(user.updatedAt).to.be.afterTime(oldDate)
done()
})
})
}, 1000)
})
})
})
})
describe('decrement', function () {
......@@ -344,10 +401,32 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).complete(done)
})
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { number: Support.Sequelize.INTEGER })
User.sync({ force: true }).success(function() {
User.create({ number: 3 }).success(function(user) {
sequelize.transaction(function(t) {
user.decrement('number', { by: 2, transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1[0].number).to.equal(3)
expect(users2[0].number).to.equal(1)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
it('with array', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement(['aNumber'], 2).complete(function() {
user1.decrement(['aNumber'], { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(-2)
done()
......@@ -359,7 +438,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it('with single field', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement('aNumber', 2).complete(function() {
user1.decrement('aNumber', { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(-2)
done()
......@@ -368,6 +447,18 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('with single field and no value', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement('aNumber').complete(function() {
self.User.find(1).complete(function(err, user2) {
expect(user2.aNumber).to.be.equal(-1)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
......@@ -376,7 +467,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).complete(function () {
user1.decrement(['aNumber'], 2).complete(function() {
user1.decrement(['aNumber'], { by: 2 }).complete(function() {
self.User.find(1).complete(function(err, user5) {
expect(user5.aNumber).to.be.equal(-1)
done()
......@@ -397,9 +488,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
user1.decrement(['aNumber'], 2).complete(_done)
user1.decrement(['aNumber'], 2).complete(_done)
user1.decrement(['aNumber'], 2).complete(_done)
user1.decrement(['aNumber'], { by: 2 }).complete(_done)
user1.decrement(['aNumber'], { by: 2 }).complete(_done)
user1.decrement(['aNumber'], { by: 2 }).complete(_done)
})
})
......@@ -415,9 +506,51 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it('with timestamps set to true', function (done) {
var User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true })
User.sync({ force: true }).success(function() {
User.create({aNumber: 1}).success(function (user) {
var oldDate = user.updatedAt
setTimeout(function () {
user.decrement('aNumber', { by: 1 }).success(function() {
User.find(1).success(function (user) {
expect(user.updatedAt).to.be.afterTime(oldDate)
done()
})
})
}, 1000)
})
})
})
})
describe('reload', function () {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
sequelize.transaction(function(t) {
User.update({ username: 'bar' }, {}, { transaction: t }).success(function() {
user.reload().success(function(user) {
expect(user.username).to.equal('foo')
user.reload({ transaction: t }).success(function(user) {
expect(user.username).to.equal('bar')
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
it("should return a reference to the same DAO instead of creating a new one", function(done) {
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
originalUser.updateAttributes({ username: 'Doe John' }).complete(function() {
......@@ -504,6 +637,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
describe('default values', function() {
describe('uuid', function() {
it('should store a string in uuidv1 and uuidv4', function(done) {
var user = this.User.build({ username: 'a user'})
expect(user.uuidv1).to.be.a('string')
expect(user.uuidv4).to.be.a('string')
done()
})
it('should store a string of length 36 in uuidv1 and uuidv4', function(done) {
var user = this.User.build({ username: 'a user'})
expect(user.uuidv1).to.have.length(36)
expect(user.uuidv4).to.have.length(36)
done()
})
it('should store a valid uuid in uuidv1 and uuidv4 that can be parsed to something of length 16', function(done) {
var user = this.User.build({ username: 'a user'})
expect(uuid.parse(user.uuidv1)).to.have.length(16)
expect(uuid.parse(user.uuidv4)).to.have.length(16)
done()
})
})
describe('current date', function() {
it('should store a date in touchedAt', function(done) {
var user = this.User.build({ username: 'a user'})
......@@ -564,6 +717,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
describe('save', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
User.sync({ force: true }).success(function() {
sequelize.transaction(function(t) {
User.build({ username: 'foo' }).save({ transaction: t }).success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(0)
expect(count2).to.equal(1)
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
it('only updates fields in passed array', function(done) {
var self = this
, userId = null
......@@ -1164,6 +1337,28 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
describe('updateAttributes', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
sequelize.transaction(function(t) {
user.updateAttributes({ username: 'bar' }, { transaction: t }).success(function() {
User.all().success(function(users1) {
User.all({ transaction: t }).success(function(users2) {
expect(users1[0].username).to.equal('foo')
expect(users2[0].username).to.equal('bar')
t.rollback().success(function(){ done() })
})
})
})
})
})
})
})
})
it("updates attributes in the database", function(done) {
this.User.create({ username: 'user' }).success(function(user) {
expect(user.username).to.equal('user')
......@@ -1274,4 +1469,69 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
describe('destroy', function() {
it('supports transactions', function(done) {
Support.prepareTransactionTest(this.sequelize, function(sequelize) {
var User = sequelize.define('User', { username: Support.Sequelize.STRING })
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
sequelize.transaction(function(t) {
user.destroy({ transaction: t }).success(function() {
User.count().success(function(count1) {
User.count({ transaction: t }).success(function(count2) {
expect(count1).to.equal(1)
expect(count2).to.equal(0)
t.rollback().success(function() { done() })
})
})
})
})
})
})
})
})
it('deletes a record from the database if dao is not paranoid', function(done) {
var UserDestroy = this.sequelize.define('UserDestroy', {
name: Support.Sequelize.STRING,
bio: Support.Sequelize.TEXT
})
UserDestroy.sync({ force: true }).success(function() {
UserDestroy.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().success(function() {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(0)
done()
})
})
})
})
})
})
it('allows sql logging of delete statements', function(done) {
var UserDelete = this.sequelize.define('UserDelete', {
name: Support.Sequelize.STRING,
bio: Support.Sequelize.TEXT
})
UserDelete.sync({ force: true }).success(function() {
UserDelete.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDelete.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("DELETE")).to.be.above(-1)
done()
})
})
})
})
})
})
})
......@@ -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'})
done()
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)
})
})
})
......
var chai = require('chai')
, expect = chai.expect
, 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')
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
......@@ -38,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
......@@ -201,6 +237,13 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
it('replaces named parameters with the passed object and ignore those which does not qualify', function(done) {
this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }])
done()
})
})
it('replaces named parameters with the passed object using the same key twice', function(done) {
this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) {
expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }])
......@@ -261,6 +304,18 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
done()
})
})
if (Support.getTestDialect() === 'postgres') {
it('supports WITH queries', function(done) {
this
.sequelize
.query("WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t")
.success(function(results) {
expect(results).to.deep.equal([ { "sum": "5050" } ])
done()
})
})
}
})
describe('define', function() {
......@@ -341,24 +396,56 @@ 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 })
var User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT })
User2.sync().error(function(err) {
if (dialect === "postgres" || dialect === "postgres-native") {
expect(err.message).to.equal('role "bar" does not exist')
} else {
expect(err.message.toString()).to.match(/.*Access\ denied.*/)
}
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) {
this.sequelize.options.logging = false
done()
})
beforeEach(function(done) {
this.spy = sinon.spy()
var self = this
this.sequelize.options.logging = function() { self.spy() }
this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING })
done()
})
it('through Sequelize.sync()', function(done) {
var self = this
this.sequelize.sync({ force: true, logging: false }).success(function() {
expect(self.spy.notCalled).to.be.true
done()
})
})
it('through DAOFactory.sync()', function(done) {
var self = this
this.User.sync({ force: true, logging: false }).success(function() {
expect(self.spy.notCalled).to.be.true
done()
})
})
})
})
......@@ -465,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()
})
})
})
}
})
})
})
......@@ -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!