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

Commit 33f46913 by Mick Hansen

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

2 parents 28461682 559ccac6
......@@ -37,17 +37,9 @@ matrix:
fast_finish: true
include:
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=mysql
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=postgres
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=sqlite
- node_js: "0.10"
env: COVERAGE=true
allow_failures:
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=mysql
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=postgres
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=sqlite
\ No newline at end of file
- node_js: "0.10"
env: COVERAGE=true
\ No newline at end of file
......@@ -11,18 +11,20 @@ teaser:
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo ''
ifeq (true,$(COVERAGE))
test: coveralls
else
test:
@if [ -n "$$COVERAGE" ]; then \
make cover && make coveralls-send; \
@elif [ "$$GREP" ]; \ then \
@if [ "$$GREP" ]; then \
make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
else \
make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi
endif
cover:
rm -rf coverage \
make teaser && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -t 10000 -R spec $(TESTS); \
make teaser && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -t 10000 $(TESTS); \
mariadb:
@DIALECT=mariadb make test
......@@ -40,18 +42,23 @@ binary:
mariadb-cover:
rm -rf coverage
@DIALECT=mariadb make cover
mv coverage coverage-mariadb
sqlite-cover:
rm -rf coverage
@DIALECT=sqlite make cover
mv coverage coverage-sqlite
mysql-cover:
rm -rf coverage
@DIALECT=mysql make cover
mv coverage coverage-mysql
postgres-cover:
rm -rf coverage
@DIALECT=postgres make cover
mv coverage coverage-postgres
postgres-native-cover:
rm -rf coverage
@DIALECT=postgres-native make cover
mv coverage coverage-postgresnative
binary-cover:
rm -rf coverage
@./test/binary/sequelize.test.bats
......@@ -62,7 +69,7 @@ merge-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
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage*
# test aliases
......
# Sequelize [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sequelize/sequelize/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
# Sequelize
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sequelize/sequelize/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Coverage Status](https://img.shields.io/coveralls/sequelize/sequelize.svg)](https://coveralls.io/r/sequelize/sequelize?branch=master)[![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
MySQL, MariaDB, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
......
......@@ -6,6 +6,8 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
- [FEATURE] It's now possible to add multiple relations to a hasMany association, modelInstance.addRelations([otherInstanceA, otherInstanceB])
- [FEATURE] `define()` stores models in `sequelize.models` Object e.g. `sequelize.models.MyModel`
- [FEATURE] The `set` / `add` / `has` methods for associations now allow you to pass the value of a primary key, instead of a full Instance object, like so: `user.addTask(15);`.
- [FEATURE] Support for FOR UPDATE and FOR SHARE statements [#1777](https://github.com/sequelize/sequelize/pull/1777)
- [FEATURE] n:m createAssocation now returns the target model instance instead of the join model instance
- [BUG] An error is now thrown if an association would create a naming conflict between the association and the foreign key when doing eager loading. Closes [#1272](https://github.com/sequelize/sequelize/issues/1272)
- [INTERNALS] `bulkDeleteQuery` was removed from the MySQL / abstract query generator, since it was never used internally. Please use `deleteQuery` instead.
......
......@@ -49,17 +49,25 @@ module.exports = (function() {
*/
this.associationAccessor = this.as
if (!this.associationAccessor && (typeof this.through === "string" || Object(this.through) === this.through)) {
this.associationAccessor = this.through.tableName || this.through
}
else if (!this.associationAccessor) {
if (!this.associationAccessor) {
if (typeof this.through === 'string') {
this.associationAccessor = this.through
} else if (Object(this.through) === this.through) {
this.associationAccessor = this.through.tableName
} else {
this.associationAccessor = this.combinedTableName
}
}
/*
* If self association, this is the target association - Unless we find a pairing association
*/
if (this.isSelfAssociation) {
// check 'as' is defined for many-to-many self-association
if (this.through && this.through !== true && !this.as) {
throw new Error('\'as\' must be defined for many-to-many self-associations')
}
this.targetAssociation = this
}
......@@ -69,15 +77,15 @@ module.exports = (function() {
if (this.through) {
_.each(this.target.associations, function (association, accessor) {
if (self.source === association.target) {
var paired = false
var paired
// If through is default, we determine pairing by the accesor value (i.e. DAOFactory's using as won't pair, but regular ones will)
if (self.through === true && accessor === self.associationAccessor) {
paired = true
if (self.through === true) {
paired = accessor === self.associationAccessor
}
// If through is not default, determine pairing by through value (model/string)
if (self.through !== true && self.options.through === association.options.through) {
paired = true
else {
paired = self.options.through === association.options.through
}
// If paired, set properties identifying both associations as double linked, and allow them to each eachtoerh
if (paired) {
......@@ -90,14 +98,13 @@ module.exports = (function() {
}
})
}
/*
* If we are double linked, and through is either default or a string, we create the through model and set it on both associations
*/
if (this.doubleLinked) {
if (this.through === true) {
if (this.doubleLinked && this.through === true) {
this.through = this.combinedTableName
}
}
if (typeof this.through === "string") {
this.through = this.sequelize.define(this.through, {}, _.extend(this.options, {
......@@ -150,10 +157,12 @@ module.exports = (function() {
if ((this.isSelfAssociation && Object(this.through) === this.through) || doubleLinked) {
// We need to remove the keys that 1:M have added
if (this.isSelfAssociation && doubleLinked) {
if (self.through.rawAttributes[this.targetAssociation.identifier]._autoGenerated) {
if (self.through.rawAttributes[this.targetAssociation.identifier]
&& self.through.rawAttributes[this.targetAssociation.identifier]._autoGenerated) {
delete self.through.rawAttributes[this.targetAssociation.identifier];
}
if (self.through.rawAttributes[this.targetAssociation.foreignIdentifier]._autoGenerated) {
if (self.through.rawAttributes[this.targetAssociation.foreignIdentifier]
&& self.through.rawAttributes[this.targetAssociation.foreignIdentifier]._autoGenerated) {
delete self.through.rawAttributes[this.targetAssociation.foreignIdentifier];
}
}
......@@ -161,19 +170,19 @@ module.exports = (function() {
this.foreignIdentifier = this.targetAssociation.identifier
this.targetAssociation.foreignIdentifier = this.identifier
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier]
}
if (this.isSelfAssociation && this.foreignIdentifier === this.identifier) {
this.foreignIdentifier = Utils._.camelizeIf(
[Utils.singularize(this.as, this.source.options.language), this.source.primaryKeyAttribute].join("_"),
!this.source.options.underscored
);
}
)
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier]
if (doubleLinked) {
this.targetAssociation.identifier = this.foreignIdentifier
}
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.targetAssociation.source.rawAttributes[this.identifier]
}
// remove any PKs previously defined by sequelize
......@@ -185,8 +194,7 @@ module.exports = (function() {
})
// define a new model, which connects the models
var combinedTableAttributes = {}
, sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
var sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
, targetKeyType = this.target.rawAttributes[this.target.primaryKeyAttribute].type
, sourceAttribute = { type: sourceKeyType }
, targetAttribute = { type: targetKeyType }
......@@ -206,15 +214,11 @@ module.exports = (function() {
if (primaryKeyDeleted) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true
} else {
var uniqueKey = [this.through.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_')
targetAttribute.unique = sourceAttribute.unique = uniqueKey
}
combinedTableAttributes[this.identifier] = sourceAttribute
combinedTableAttributes[this.foreignIdentifier] = targetAttribute
if (!this.through.rawAttributes[this.identifier]) {
this.through.rawAttributes[this.identifier] = {
_autoGenerated: true
......@@ -227,8 +231,8 @@ module.exports = (function() {
}
}
this.through.rawAttributes[this.identifier] = Utils._.merge(this.through.rawAttributes[this.identifier], sourceAttribute);
this.through.rawAttributes[this.foreignIdentifier] = Utils._.merge(this.through.rawAttributes[this.foreignIdentifier], targetAttribute);
this.through.rawAttributes[this.identifier] = Utils._.extend(this.through.rawAttributes[this.identifier], sourceAttribute);
this.through.rawAttributes[this.foreignIdentifier] = Utils._.extend(this.through.rawAttributes[this.foreignIdentifier], targetAttribute);
this.through.init(this.through.daoFactoryManager)
} else {
......@@ -427,6 +431,10 @@ module.exports = (function() {
var instance = this
, options = {}
if (values === undefined) {
values = {}
}
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction
}
......@@ -434,7 +442,7 @@ module.exports = (function() {
if (Object(association.through) === association.through) {
// Create the related model instance
return association.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
return instance[association.accessors.add](newAssociatedObject, options)
return instance[association.accessors.add](newAssociatedObject, options).return(newAssociatedObject)
})
} else {
values[association.identifier] = instance.get(association.source.primaryKeyAttribute);
......
......@@ -3,10 +3,10 @@ var Utils = require("./../utils")
module.exports = {
addForeignKeyConstraints: function(newAttribute, source, target, options) {
// FK constraints are opt-in: users must either rset `foreignKeyConstraints`
// FK constraints are opt-in: users must either set `foreignKeyConstraints`
// on the association, or request an `onDelete` or `onUpdate` behaviour
if(options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.filter(Utils._.keys(source.rawAttributes), function(key) {
......
......@@ -78,7 +78,7 @@ var Mixin = module.exports = function(){}
*
* The following methods are injected on the source:
*
* * get[AS] - for example getProfile()
* * get[AS] - for example getProfile(finder). The finder object is passed to `target.find`.
* * set[AS] - for example setProfile(instance, options). Options are passed to `target.save`
* * create[AS] - for example createProfile(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to `target.create`
*
......@@ -119,7 +119,7 @@ Mixin.hasOne = function(targetModel, options) {
*
* The following methods are injected on the source:
*
* * get[AS] - for example getUser()
* * get[AS] - for example getUser(finder). The finder object is passed to `target.find`.
* * set[AS] - for example setUser(instance, options). Options are passed to this.save
* * create[AS] - for example createUser(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to target.create
*
......@@ -169,7 +169,7 @@ Mixin.belongsTo = function(targetModel, options) {
* The following methods are injected on the source:
*
* * get[AS] - for example getPictures().
* * get[AS] - for example getPictures(finder). The finder object is passed to `target.find`.
* * set[AS] - for example setPictures(instances, defaultAttributes|options). Update the associations. All currently associated models that are not in instances will be removed.
* * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another associated object.
* * add[AS] [plural] - for example addPictures([instance1, instance2], defaultAttributes|options). Add some more associated objects.
......
......@@ -956,6 +956,14 @@ module.exports = (function() {
query = mainQueryItems.join('')
}
if (options.lock && this._dialect.supports.lock) {
if (options.lock === 'SHARE') {
query += ' ' + this._dialect.supports.forShare
} else {
query += ' FOR UPDATE'
}
}
query += ";";
return query
......
......@@ -13,7 +13,7 @@ module.exports = (function() {
mysql = require('mysql')
}
} catch (err) {
console.log('You need to install mysql package manually')
throw new Error('Please install mysql package manually')
}
this.sequelize = sequelize
......
......@@ -7,7 +7,9 @@ var MysqlDialect = function(sequelize) {
MysqlDialect.prototype.supports = _.defaults({
'VALUES ()': true,
'LIMIT ON UPDATE':true
'LIMIT ON UPDATE':true,
lock: true,
forShare: 'LOCK IN SHARE MODE'
}, Abstract.prototype.supports)
module.exports = MysqlDialect
......@@ -8,7 +8,9 @@ var PostgresDialect = function(sequelize) {
PostgresDialect.prototype.supports = _.defaults({
'RETURNING': true,
'DEFAULT VALUES': true,
schemas: true
schemas: true,
lock: true,
forShare: 'FOR SHARE',
}, Abstract.prototype.supports)
module.exports = PostgresDialect
......@@ -400,7 +400,7 @@ module.exports = (function() {
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Transaction} [options.transaction]
*
* @return {Promise}
* @return {Promise<this>}
*/
Instance.prototype.save = function(fieldsOrOptions, options) {
if (fieldsOrOptions instanceof Array) {
......@@ -571,7 +571,7 @@ module.exports = (function() {
*
* @see {Model#find}
* @param {Object} [options] Options that are passed on to `Model.find`
* @return {Promise}
* @return {Promise<this>}
*/
Instance.prototype.reload = function(options) {
var self = this
......@@ -598,7 +598,7 @@ module.exports = (function() {
* @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated
* @see {InstanceValidator}
*
* @return {Promise}
* @return {Promise<undefined|Error}
*/
Instance.prototype.validate = function(options) {
return new InstanceValidator(this, options).validate()
......@@ -618,7 +618,7 @@ module.exports = (function() {
* @param {Object} updates See `setAttributes`
* @param {Object} options See `save`
*
* @return {Promise}
* @return {Promise<this>}
*/
Instance.prototype.updateAttributes = function(updates, options) {
if (options instanceof Array) {
......@@ -639,7 +639,7 @@ module.exports = (function() {
* @param {Object} [options={}]
* @param {Boolean} [options.force=false] If set to true, paranoid models will actually be deleted
*
* @return {Promise}
* @return {Promise<undefined>}
*/
Instance.prototype.destroy = function(options) {
options = options || {}
......
......@@ -382,7 +382,7 @@ module.exports = (function() {
/**
* Sync this Model to the DB, that is create the table. Upon success, the callback will be called with the model instance (this)
* @see {Sequelize#sync} for options
* @return {Promise}
* @return {Promise<this>}
*/
Model.prototype.sync = function(options) {
options = Utils._.extend({}, this.options, options || {})
......@@ -643,9 +643,10 @@ module.exports = (function() {
* @param {Number} [options.offset]
* @param {Object} [queryOptions] Set the query options, e.g. raw, specifying that you want raw data instead of built Instances. See sequelize.query for options
* @param {Transaction} [queryOptions.transaction]
* @param {String} [queryOptions.lock] Lock the selected rows in either share or update mode. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. See [transaction.LOCK for an example](https://github.com/sequelize/sequelize/wiki/API-Reference-Transaction#LOCK)
*
* @see {Sequelize#query}
* @return {Promise}
* @return {Promise<Array<Instance>>}
* @alias all
*/
Model.prototype.findAll = function(options, queryOptions) {
......@@ -706,7 +707,7 @@ module.exports = (function() {
* @param {Object} [queryOptions]
*
* @see {Model#findAll} for an explanation of options and queryOptions
* @return {Promise}
* @return {Promise<Instance>}
*/
Model.prototype.find = function(options, queryOptions) {
var hasJoin = false
......@@ -800,9 +801,9 @@ module.exports = (function() {
* @param {String} field The field to aggregate over. Can be a field name or *
* @param {String} aggregateFunction The function to use for aggregation, e.g. sum, max etc.
* @param {Object} [options] Query options. See sequelize.query for full options
* @param {DataType|String} [options.dataType] The type of the result. If field is a field in the Instance, the default will be the type of that field, otherwise defaults to float.
* @param {DataType|String} [options.dataType] The type of the result. If `field` is a field in this Model, the default will be the type of that field, otherwise defaults to float.
*
* @return {Promise}
* @return {Promise<options.dataType>}
*/
Model.prototype.aggregate = function(field, aggregateFunction, options) {
options = Utils._.extend({ attributes: [] }, options || {})
......@@ -831,7 +832,7 @@ module.exports = (function() {
* @param {Object} [options]
* @param {Object} [options.include] Include options. See `find` for details
*
* @return {Promise}
* @return {Promise<Integer>}
*/
Model.prototype.count = function(options) {
options = Utils._.clone(options || {})
......@@ -866,7 +867,7 @@ module.exports = (function() {
* @param {Object} [queryOptions] See Sequelize.query
*
* @see {Model#findAll} for a specification of find and query options
* @return {Promise}
* @return {Promise<Object>}
*/
Model.prototype.findAndCountAll = function(findOptions, queryOptions) {
var self = this
......@@ -896,7 +897,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate
* @see {Model#aggregate} for options
*
* @return {Promise}
* @return {Promise<Any>}
*/
Model.prototype.max = function(field, options) {
return this.aggregate(field, 'max', options)
......@@ -909,7 +910,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate
* @see {Model#aggregate} for options
*
* @return {Promise}
* @return {Promise<Any>}
*/
Model.prototype.min = function(field, options) {
return this.aggregate(field, 'min', options)
......@@ -922,7 +923,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate
* @see {Model#aggregate} for options
*
* @return {Promise}
* @return {Promise<Number>}
*/
Model.prototype.sum = function(field, options) {
return this.aggregate(field, 'sum', options)
......@@ -993,7 +994,7 @@ module.exports = (function() {
* @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances
* @param {Transaction} [options.transaction]
*
* @return {Promise}
* @return {Promise<Instance>}
*/
Model.prototype.create = function(values, options) {
Utils.validateParameter(values, Object, { optional: true })
......@@ -1021,8 +1022,7 @@ module.exports = (function() {
* @param {Object} [options] Options passed to the find call
* @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API
*
* @return {Promise}
* @return {EventEmitter}
* @return {Promise<Instance>}
* @method
* @alias findOrBuild
*/
......@@ -1069,7 +1069,7 @@ module.exports = (function() {
* @param {Object} [options] Options passed to the find and create calls
* @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API
*
* @return {Promise}
* @return {Promise<Instance>}
*/
Model.prototype.findOrCreate = function (where, defaults, options) {
var self = this
......@@ -1118,7 +1118,7 @@ module.exports = (function() {
* @param {Boolean} [options.hooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run.
* @param {Boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by postgres)
*
* @return {Promise}
* @return {Promise<Array<Instance>>}
*/
Model.prototype.bulkCreate = function(records, fieldsOrOptions, options) {
Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'Model#bulkCreate' })
......@@ -1256,7 +1256,7 @@ module.exports = (function() {
* @param {Number} [options.limit] How many rows to delete
* @param {Boolean} [options.truncate] If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is truncated the where and limit options are ignored
*
* @return {Promise}
* @return {Promise<undefined>}
*/
Model.prototype.destroy = function(where, options) {
options = options || {}
......@@ -1339,7 +1339,7 @@ module.exports = (function() {
* @param {Number} [options.limit] How many rows to update (only for mysql and mariadb)
* @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API
*
* @return {EventEmitter}
* @return {Promise}
*/
Model.prototype.update = function(attrValueHash, where, options) {
var self = this
......@@ -1432,7 +1432,7 @@ module.exports = (function() {
/**
* Run a describe query on the table. The result will be return to the listener as a hash of attributes and their types.
*
* @return {EventEmitter}
* @return {Promise}
*/
Model.prototype.describe = function(schema) {
return this.QueryInterface.describeTable(this.tableName, schema || this.options.schema || undefined)
......
......@@ -552,6 +552,8 @@ module.exports = (function() {
})
}
options.lock = queryOptions.lock
var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, {
include: options.include,
......
......@@ -40,6 +40,26 @@ Transaction.ISOLATION_LEVELS = {
}
/**
* Possible options for row locking. Used in conjuction with `find` calls:
*
* ```js
* t1 // is a transaction
* Model.findAll({
* where: ...
* }, {
* transaction: t1,
* lock: t1.LOCK.UPDATE,
* lock: t1.LOCK.SHARE
* })
* ```
* @property LOCK
*/
Transaction.LOCK = Transaction.prototype.LOCK = {
UPDATE: 'UPDATE',
SHARE: 'SHARE'
}
/**
* Commit the transaction
*
* @return {this}
......
......@@ -998,7 +998,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.sequelize.sync({ force: true }).success(function() {
Task.create({ title: 'task' }).success(function(task) {
task.createUser({ username: 'foo' }).success(function() {
task.createUser({ username: 'foo' }).success(function(createdUser) {
expect(createdUser.Model).to.equal(User)
expect(createdUser.username).to.equal('foo')
task.getUsers().success(function(_users) {
expect(_users).to.have.length(1)
......
......@@ -57,7 +57,41 @@ describe(Support.getTestDialectTeaser("Self"), function() {
});
});
it('can handle n:m associations', function(done) {
it('can handle n:m associations', function() {
var self = this
var Person = this.sequelize.define('Person', { name: DataTypes.STRING })
Person.hasMany(Person, { as: 'Parents', through: 'Family' })
Person.hasMany(Person, { as: 'Childs', through: 'Family' })
var foreignIdentifiers = _.map(_.values(Person.associations), 'foreignIdentifier')
var rawAttributes = _.keys(this.sequelize.models.Family.rawAttributes)
expect(foreignIdentifiers.length).to.equal(2)
expect(rawAttributes.length).to.equal(4)
expect(foreignIdentifiers).to.have.members([ 'PersonId', 'ChildId' ])
expect(rawAttributes).to.have.members([ 'createdAt', 'updatedAt', 'PersonId', 'ChildId' ])
return this.sequelize.sync({ force: true }).then(function() {
return self.sequelize.Promise.all([
Person.create({ name: 'Mary' }),
Person.create({ name: 'John' }),
Person.create({ name: 'Chris' })
]).spread(function (mary, john, chris) {
return mary.setParents([john]).then(function() {
return chris.addParent(john)
}).then(function() {
return john.getChilds()
}).then(function(children) {
expect(_.map(children, 'id')).to.have.members([mary.id, chris.id])
})
})
})
})
it('can handle n:m associations with pre-defined through table', function(done) {
var Person = this.sequelize.define('Person', { name: DataTypes.STRING });
var Family = this.sequelize.define('Family', {
preexisting_child: {
......
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, dialect = Support.getTestDialect()
, Transaction = require(__dirname + '/../lib/transaction')
, sinon = require('sinon')
describe(Support.getTestDialectTeaser("Transaction"), function () {
this.timeout(4000);
......@@ -71,4 +74,122 @@ describe(Support.getTestDialectTeaser("Transaction"), function () {
})
})
})
if (dialect !== 'sqlite') {
describe('row locking', function () {
this.timeout(10000);
it('supports for update', function (done) {
var User = this.sequelize.define('user', {
username: Support.Sequelize.STRING,
awesome: Support.Sequelize.BOOLEAN
})
, self = this
, t1Spy = sinon.spy()
, t2Spy = sinon.spy()
this.sequelize.sync({ force: true }).then(function () {
return User.create({ username: 'jan'})
}).then(function () {
self.sequelize.transaction(function (t1) {
return User.find({
where: {
username: 'jan'
}
}, {
lock: t1.LOCK.UPDATE,
transaction: t1
}).then(function (t1Jan) {
self.sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED
}, function (t2) {
User.find({
where: {
username: 'jan'
},
}, {
lock: t2.LOCK.UPDATE,
transaction: t2
}).then(function () {
t2Spy()
t2.commit().then(function () {
expect(t2Spy).to.have.been.calledAfter(t1Spy) // Find should not succeed before t1 has comitted
done()
})
})
t1Jan.updateAttributes({
awesome: true
}, { transaction: t1}).then(function () {
t1Spy()
setTimeout(t1.commit.bind(t1), 2000)
})
})
})
})
})
})
it('supports for share', function (done) {
var User = this.sequelize.define('user', {
username: Support.Sequelize.STRING,
awesome: Support.Sequelize.BOOLEAN
})
, self = this
, t1Spy = sinon.spy()
, t2FindSpy = sinon.spy()
, t2UpdateSpy = sinon.spy()
this.sequelize.sync({ force: true }).then(function () {
return User.create({ username: 'jan'})
}).then(function () {
self.sequelize.transaction(function (t1) {
return User.find({
where: {
username: 'jan'
}
}, {
lock: t1.LOCK.SHARE,
transaction: t1
}).then(function (t1Jan) {
self.sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED
}, function (t2) {
User.find({
where: {
username: 'jan'
}
}, { transaction: t2}).then(function (t2Jan) {
t2FindSpy()
t2Jan.updateAttributes({
awesome: false
}, {
transaction: t2
}).then(function () {
t2UpdateSpy()
t2.commit().then(function () {
expect(t2FindSpy).to.have.been.calledBefore(t1Spy) // The find call should have returned
expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy) // But the update call should not happen before the first transaction has committed
done()
})
})
})
t1Jan.updateAttributes({
awesome: true
}, {
transaction: t1
}).then(function () {
setTimeout(function () {
t1Spy()
t1.commit()
}, 2000)
})
})
})
})
})
})
})
}
})
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!