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

Commit 33f46913 by Mick Hansen

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

2 parents 28461682 559ccac6
...@@ -37,17 +37,9 @@ matrix: ...@@ -37,17 +37,9 @@ matrix:
fast_finish: true fast_finish: true
include: include:
- node_js: "0.8" - node_js: "0.10"
env: COVERAGE=true DB=mysql DIALECT=mysql env: COVERAGE=true
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=postgres
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=sqlite
allow_failures: allow_failures:
- node_js: "0.8" - node_js: "0.10"
env: COVERAGE=true DB=mysql DIALECT=mysql env: COVERAGE=true
- node_js: "0.8" \ No newline at end of file
env: COVERAGE=true DB=mysql DIALECT=postgres
- node_js: "0.8"
env: COVERAGE=true DB=mysql DIALECT=sqlite
\ No newline at end of file
...@@ -11,18 +11,20 @@ teaser: ...@@ -11,18 +11,20 @@ teaser:
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \ node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo '' echo ''
ifeq (true,$(COVERAGE))
test: coveralls
else
test: test:
@if [ -n "$$COVERAGE" ]; then \ @if [ "$$GREP" ]; then \
make cover && make coveralls-send; \
@elif [ "$$GREP" ]; \ then \
make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
else \ else \
make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --globals setImmediate,clearImmediate --check-leaks --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi fi
endif
cover: cover:
rm -rf coverage \ 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: mariadb:
@DIALECT=mariadb make test @DIALECT=mariadb make test
...@@ -40,18 +42,23 @@ binary: ...@@ -40,18 +42,23 @@ binary:
mariadb-cover: mariadb-cover:
rm -rf coverage rm -rf coverage
@DIALECT=mariadb make cover @DIALECT=mariadb make cover
mv coverage coverage-mariadb
sqlite-cover: sqlite-cover:
rm -rf coverage rm -rf coverage
@DIALECT=sqlite make cover @DIALECT=sqlite make cover
mv coverage coverage-sqlite
mysql-cover: mysql-cover:
rm -rf coverage rm -rf coverage
@DIALECT=mysql make cover @DIALECT=mysql make cover
mv coverage coverage-mysql
postgres-cover: postgres-cover:
rm -rf coverage rm -rf coverage
@DIALECT=postgres make cover @DIALECT=postgres make cover
mv coverage coverage-postgres
postgres-native-cover: postgres-native-cover:
rm -rf coverage rm -rf coverage
@DIALECT=postgres-native make cover @DIALECT=postgres-native make cover
mv coverage coverage-postgresnative
binary-cover: binary-cover:
rm -rf coverage rm -rf coverage
@./test/binary/sequelize.test.bats @./test/binary/sequelize.test.bats
...@@ -62,7 +69,7 @@ merge-coverage: ...@@ -62,7 +69,7 @@ merge-coverage:
./node_modules/.bin/lcov-result-merger 'coverage-*/lcov.info' 'coverage/lcov.info' ./node_modules/.bin/lcov-result-merger 'coverage-*/lcov.info' 'coverage/lcov.info'
coveralls-send: 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 # 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). 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 ...@@ -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] 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] `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] 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) - [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. - [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() { ...@@ -49,17 +49,25 @@ module.exports = (function() {
*/ */
this.associationAccessor = this.as this.associationAccessor = this.as
if (!this.associationAccessor && (typeof this.through === "string" || Object(this.through) === this.through)) { if (!this.associationAccessor) {
this.associationAccessor = this.through.tableName || this.through if (typeof this.through === 'string') {
} this.associationAccessor = this.through
else if (!this.associationAccessor) { } else if (Object(this.through) === this.through) {
this.associationAccessor = this.combinedTableName 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 self association, this is the target association - Unless we find a pairing association
*/ */
if (this.isSelfAssociation) { 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 this.targetAssociation = this
} }
...@@ -69,15 +77,15 @@ module.exports = (function() { ...@@ -69,15 +77,15 @@ module.exports = (function() {
if (this.through) { if (this.through) {
_.each(this.target.associations, function (association, accessor) { _.each(this.target.associations, function (association, accessor) {
if (self.source === association.target) { 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 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) { if (self.through === true) {
paired = true paired = accessor === self.associationAccessor
} }
// If through is not default, determine pairing by through value (model/string) // If through is not default, determine pairing by through value (model/string)
if (self.through !== true && self.options.through === association.options.through) { else {
paired = true 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, set properties identifying both associations as double linked, and allow them to each eachtoerh
if (paired) { if (paired) {
...@@ -90,13 +98,12 @@ module.exports = (function() { ...@@ -90,13 +98,12 @@ 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 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.doubleLinked && this.through === true) {
if (this.through === true) { this.through = this.combinedTableName
this.through = this.combinedTableName
}
} }
if (typeof this.through === "string") { if (typeof this.through === "string") {
...@@ -150,10 +157,12 @@ module.exports = (function() { ...@@ -150,10 +157,12 @@ module.exports = (function() {
if ((this.isSelfAssociation && Object(this.through) === this.through) || doubleLinked) { if ((this.isSelfAssociation && Object(this.through) === this.through) || doubleLinked) {
// We need to remove the keys that 1:M have added // We need to remove the keys that 1:M have added
if (this.isSelfAssociation && doubleLinked) { 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]; 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]; delete self.through.rawAttributes[this.targetAssociation.foreignIdentifier];
} }
} }
...@@ -161,19 +170,19 @@ module.exports = (function() { ...@@ -161,19 +170,19 @@ module.exports = (function() {
this.foreignIdentifier = this.targetAssociation.identifier this.foreignIdentifier = this.targetAssociation.identifier
this.targetAssociation.foreignIdentifier = this.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) { if (this.isSelfAssociation && this.foreignIdentifier === this.identifier) {
this.foreignIdentifier = Utils._.camelizeIf( this.foreignIdentifier = Utils._.camelizeIf(
[Utils.singularize(this.as, this.source.options.language), this.source.primaryKeyAttribute].join("_"), [Utils.singularize(this.as, this.source.options.language), this.source.primaryKeyAttribute].join("_"),
!this.source.options.underscored !this.source.options.underscored
); )
}
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier]
}
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) { if (doubleLinked) {
delete this.targetAssociation.source.rawAttributes[this.identifier] this.targetAssociation.identifier = this.foreignIdentifier
}
} }
// remove any PKs previously defined by sequelize // remove any PKs previously defined by sequelize
...@@ -185,8 +194,7 @@ module.exports = (function() { ...@@ -185,8 +194,7 @@ module.exports = (function() {
}) })
// define a new model, which connects the models // define a new model, which connects the models
var combinedTableAttributes = {} var sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
, sourceKeyType = this.source.rawAttributes[this.source.primaryKeyAttribute].type
, targetKeyType = this.target.rawAttributes[this.target.primaryKeyAttribute].type , targetKeyType = this.target.rawAttributes[this.target.primaryKeyAttribute].type
, sourceAttribute = { type: sourceKeyType } , sourceAttribute = { type: sourceKeyType }
, targetAttribute = { type: targetKeyType } , targetAttribute = { type: targetKeyType }
...@@ -206,15 +214,11 @@ module.exports = (function() { ...@@ -206,15 +214,11 @@ module.exports = (function() {
if (primaryKeyDeleted) { if (primaryKeyDeleted) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true targetAttribute.primaryKey = sourceAttribute.primaryKey = true
} else { } else {
var uniqueKey = [this.through.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_') var uniqueKey = [this.through.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_')
targetAttribute.unique = sourceAttribute.unique = uniqueKey targetAttribute.unique = sourceAttribute.unique = uniqueKey
} }
combinedTableAttributes[this.identifier] = sourceAttribute
combinedTableAttributes[this.foreignIdentifier] = targetAttribute
if (!this.through.rawAttributes[this.identifier]) { if (!this.through.rawAttributes[this.identifier]) {
this.through.rawAttributes[this.identifier] = { this.through.rawAttributes[this.identifier] = {
_autoGenerated: true _autoGenerated: true
...@@ -227,8 +231,8 @@ module.exports = (function() { ...@@ -227,8 +231,8 @@ module.exports = (function() {
} }
} }
this.through.rawAttributes[this.identifier] = Utils._.merge(this.through.rawAttributes[this.identifier], sourceAttribute); this.through.rawAttributes[this.identifier] = Utils._.extend(this.through.rawAttributes[this.identifier], sourceAttribute);
this.through.rawAttributes[this.foreignIdentifier] = Utils._.merge(this.through.rawAttributes[this.foreignIdentifier], targetAttribute); this.through.rawAttributes[this.foreignIdentifier] = Utils._.extend(this.through.rawAttributes[this.foreignIdentifier], targetAttribute);
this.through.init(this.through.daoFactoryManager) this.through.init(this.through.daoFactoryManager)
} else { } else {
...@@ -427,6 +431,10 @@ module.exports = (function() { ...@@ -427,6 +431,10 @@ module.exports = (function() {
var instance = this var instance = this
, options = {} , options = {}
if (values === undefined) {
values = {}
}
if ((fieldsOrOptions || {}).transaction instanceof Transaction) { if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction options.transaction = fieldsOrOptions.transaction
} }
...@@ -434,7 +442,7 @@ module.exports = (function() { ...@@ -434,7 +442,7 @@ module.exports = (function() {
if (Object(association.through) === association.through) { if (Object(association.through) === association.through) {
// Create the related model instance // Create the related model instance
return association.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) { 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 { } else {
values[association.identifier] = instance.get(association.source.primaryKeyAttribute); values[association.identifier] = instance.get(association.source.primaryKeyAttribute);
......
...@@ -3,10 +3,10 @@ var Utils = require("./../utils") ...@@ -3,10 +3,10 @@ var Utils = require("./../utils")
module.exports = { module.exports = {
addForeignKeyConstraints: function(newAttribute, source, target, options) { 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 // 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 // Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.filter(Utils._.keys(source.rawAttributes), function(key) { var primaryKeys = Utils._.filter(Utils._.keys(source.rawAttributes), function(key) {
......
...@@ -78,7 +78,7 @@ var Mixin = module.exports = function(){} ...@@ -78,7 +78,7 @@ var Mixin = module.exports = function(){}
* *
* The following methods are injected on the source: * 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` * * 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` * * 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) { ...@@ -119,7 +119,7 @@ Mixin.hasOne = function(targetModel, options) {
* *
* The following methods are injected on the source: * 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 * * 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 * * 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) { ...@@ -169,7 +169,7 @@ Mixin.belongsTo = function(targetModel, options) {
* The following methods are injected on the source: * 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. * * 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] - 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. * * add[AS] [plural] - for example addPictures([instance1, instance2], defaultAttributes|options). Add some more associated objects.
...@@ -211,7 +211,7 @@ Mixin.belongsTo = function(targetModel, options) { ...@@ -211,7 +211,7 @@ Mixin.belongsTo = function(targetModel, options) {
* }) * })
* ``` * ```
* *
* @param {Model} target * @param {Model} target
* @param {object} [options] * @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it. * @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it.
......
...@@ -956,6 +956,14 @@ module.exports = (function() { ...@@ -956,6 +956,14 @@ module.exports = (function() {
query = mainQueryItems.join('') query = mainQueryItems.join('')
} }
if (options.lock && this._dialect.supports.lock) {
if (options.lock === 'SHARE') {
query += ' ' + this._dialect.supports.forShare
} else {
query += ' FOR UPDATE'
}
}
query += ";"; query += ";";
return query return query
......
...@@ -13,7 +13,7 @@ module.exports = (function() { ...@@ -13,7 +13,7 @@ module.exports = (function() {
mysql = require('mysql') mysql = require('mysql')
} }
} catch (err) { } catch (err) {
console.log('You need to install mysql package manually') throw new Error('Please install mysql package manually')
} }
this.sequelize = sequelize this.sequelize = sequelize
......
...@@ -7,7 +7,9 @@ var MysqlDialect = function(sequelize) { ...@@ -7,7 +7,9 @@ var MysqlDialect = function(sequelize) {
MysqlDialect.prototype.supports = _.defaults({ MysqlDialect.prototype.supports = _.defaults({
'VALUES ()': true, 'VALUES ()': true,
'LIMIT ON UPDATE':true 'LIMIT ON UPDATE':true,
lock: true,
forShare: 'LOCK IN SHARE MODE'
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = MysqlDialect module.exports = MysqlDialect
...@@ -8,7 +8,9 @@ var PostgresDialect = function(sequelize) { ...@@ -8,7 +8,9 @@ var PostgresDialect = function(sequelize) {
PostgresDialect.prototype.supports = _.defaults({ PostgresDialect.prototype.supports = _.defaults({
'RETURNING': true, 'RETURNING': true,
'DEFAULT VALUES': true, 'DEFAULT VALUES': true,
schemas: true schemas: true,
lock: true,
forShare: 'FOR SHARE',
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = PostgresDialect module.exports = PostgresDialect
...@@ -400,7 +400,7 @@ module.exports = (function() { ...@@ -400,7 +400,7 @@ module.exports = (function() {
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Transaction} [options.transaction] * @param {Transaction} [options.transaction]
* *
* @return {Promise} * @return {Promise<this>}
*/ */
Instance.prototype.save = function(fieldsOrOptions, options) { Instance.prototype.save = function(fieldsOrOptions, options) {
if (fieldsOrOptions instanceof Array) { if (fieldsOrOptions instanceof Array) {
...@@ -571,7 +571,7 @@ module.exports = (function() { ...@@ -571,7 +571,7 @@ module.exports = (function() {
* *
* @see {Model#find} * @see {Model#find}
* @param {Object} [options] Options that are passed on to `Model.find` * @param {Object} [options] Options that are passed on to `Model.find`
* @return {Promise} * @return {Promise<this>}
*/ */
Instance.prototype.reload = function(options) { Instance.prototype.reload = function(options) {
var self = this var self = this
...@@ -598,7 +598,7 @@ module.exports = (function() { ...@@ -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 * @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated
* @see {InstanceValidator} * @see {InstanceValidator}
* *
* @return {Promise} * @return {Promise<undefined|Error}
*/ */
Instance.prototype.validate = function(options) { Instance.prototype.validate = function(options) {
return new InstanceValidator(this, options).validate() return new InstanceValidator(this, options).validate()
...@@ -618,7 +618,7 @@ module.exports = (function() { ...@@ -618,7 +618,7 @@ module.exports = (function() {
* @param {Object} updates See `setAttributes` * @param {Object} updates See `setAttributes`
* @param {Object} options See `save` * @param {Object} options See `save`
* *
* @return {Promise} * @return {Promise<this>}
*/ */
Instance.prototype.updateAttributes = function(updates, options) { Instance.prototype.updateAttributes = function(updates, options) {
if (options instanceof Array) { if (options instanceof Array) {
...@@ -639,7 +639,7 @@ module.exports = (function() { ...@@ -639,7 +639,7 @@ module.exports = (function() {
* @param {Object} [options={}] * @param {Object} [options={}]
* @param {Boolean} [options.force=false] If set to true, paranoid models will actually be deleted * @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) { Instance.prototype.destroy = function(options) {
options = options || {} options = options || {}
......
...@@ -382,7 +382,7 @@ module.exports = (function() { ...@@ -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) * 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 * @see {Sequelize#sync} for options
* @return {Promise} * @return {Promise<this>}
*/ */
Model.prototype.sync = function(options) { Model.prototype.sync = function(options) {
options = Utils._.extend({}, this.options, options || {}) options = Utils._.extend({}, this.options, options || {})
...@@ -487,7 +487,7 @@ module.exports = (function() { ...@@ -487,7 +487,7 @@ module.exports = (function() {
* ``` * ```
* *
* @param {Array|Object|String|null} options* The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. * @param {Array|Object|String|null} options* The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default.
* @return {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope. * @return {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope.
*/ */
Model.prototype.scope = function(option) { Model.prototype.scope = function(option) {
var self = Object.create(this) var self = Object.create(this)
...@@ -643,9 +643,10 @@ module.exports = (function() { ...@@ -643,9 +643,10 @@ module.exports = (function() {
* @param {Number} [options.offset] * @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 {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 {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} * @see {Sequelize#query}
* @return {Promise} * @return {Promise<Array<Instance>>}
* @alias all * @alias all
*/ */
Model.prototype.findAll = function(options, queryOptions) { Model.prototype.findAll = function(options, queryOptions) {
...@@ -706,7 +707,7 @@ module.exports = (function() { ...@@ -706,7 +707,7 @@ module.exports = (function() {
* @param {Object} [queryOptions] * @param {Object} [queryOptions]
* *
* @see {Model#findAll} for an explanation of options and queryOptions * @see {Model#findAll} for an explanation of options and queryOptions
* @return {Promise} * @return {Promise<Instance>}
*/ */
Model.prototype.find = function(options, queryOptions) { Model.prototype.find = function(options, queryOptions) {
var hasJoin = false var hasJoin = false
...@@ -800,9 +801,9 @@ module.exports = (function() { ...@@ -800,9 +801,9 @@ module.exports = (function() {
* @param {String} field The field to aggregate over. Can be a field name or * * @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 {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 {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) { Model.prototype.aggregate = function(field, aggregateFunction, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
...@@ -831,7 +832,7 @@ module.exports = (function() { ...@@ -831,7 +832,7 @@ module.exports = (function() {
* @param {Object} [options] * @param {Object} [options]
* @param {Object} [options.include] Include options. See `find` for details * @param {Object} [options.include] Include options. See `find` for details
* *
* @return {Promise} * @return {Promise<Integer>}
*/ */
Model.prototype.count = function(options) { Model.prototype.count = function(options) {
options = Utils._.clone(options || {}) options = Utils._.clone(options || {})
...@@ -866,7 +867,7 @@ module.exports = (function() { ...@@ -866,7 +867,7 @@ module.exports = (function() {
* @param {Object} [queryOptions] See Sequelize.query * @param {Object} [queryOptions] See Sequelize.query
* *
* @see {Model#findAll} for a specification of find and query options * @see {Model#findAll} for a specification of find and query options
* @return {Promise} * @return {Promise<Object>}
*/ */
Model.prototype.findAndCountAll = function(findOptions, queryOptions) { Model.prototype.findAndCountAll = function(findOptions, queryOptions) {
var self = this var self = this
...@@ -896,7 +897,7 @@ module.exports = (function() { ...@@ -896,7 +897,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate * @param {Object} [options] See aggregate
* @see {Model#aggregate} for options * @see {Model#aggregate} for options
* *
* @return {Promise} * @return {Promise<Any>}
*/ */
Model.prototype.max = function(field, options) { Model.prototype.max = function(field, options) {
return this.aggregate(field, 'max', options) return this.aggregate(field, 'max', options)
...@@ -909,7 +910,7 @@ module.exports = (function() { ...@@ -909,7 +910,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate * @param {Object} [options] See aggregate
* @see {Model#aggregate} for options * @see {Model#aggregate} for options
* *
* @return {Promise} * @return {Promise<Any>}
*/ */
Model.prototype.min = function(field, options) { Model.prototype.min = function(field, options) {
return this.aggregate(field, 'min', options) return this.aggregate(field, 'min', options)
...@@ -922,7 +923,7 @@ module.exports = (function() { ...@@ -922,7 +923,7 @@ module.exports = (function() {
* @param {Object} [options] See aggregate * @param {Object} [options] See aggregate
* @see {Model#aggregate} for options * @see {Model#aggregate} for options
* *
* @return {Promise} * @return {Promise<Number>}
*/ */
Model.prototype.sum = function(field, options) { Model.prototype.sum = function(field, options) {
return this.aggregate(field, 'sum', options) return this.aggregate(field, 'sum', options)
...@@ -993,7 +994,7 @@ module.exports = (function() { ...@@ -993,7 +994,7 @@ module.exports = (function() {
* @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances
* @param {Transaction} [options.transaction] * @param {Transaction} [options.transaction]
* *
* @return {Promise} * @return {Promise<Instance>}
*/ */
Model.prototype.create = function(values, options) { Model.prototype.create = function(values, options) {
Utils.validateParameter(values, Object, { optional: true }) Utils.validateParameter(values, Object, { optional: true })
...@@ -1021,8 +1022,7 @@ module.exports = (function() { ...@@ -1021,8 +1022,7 @@ module.exports = (function() {
* @param {Object} [options] Options passed to the find call * @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 * @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>}
* @return {EventEmitter}
* @method * @method
* @alias findOrBuild * @alias findOrBuild
*/ */
...@@ -1069,7 +1069,7 @@ module.exports = (function() { ...@@ -1069,7 +1069,7 @@ module.exports = (function() {
* @param {Object} [options] Options passed to the find and create calls * @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 * @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) { Model.prototype.findOrCreate = function (where, defaults, options) {
var self = this var self = this
...@@ -1118,7 +1118,7 @@ module.exports = (function() { ...@@ -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.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) * @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) { Model.prototype.bulkCreate = function(records, fieldsOrOptions, options) {
Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'Model#bulkCreate' }) Utils.validateParameter(fieldsOrOptions, Object, { deprecated: Array, optional: true, index: 2, method: 'Model#bulkCreate' })
...@@ -1256,7 +1256,7 @@ module.exports = (function() { ...@@ -1256,7 +1256,7 @@ module.exports = (function() {
* @param {Number} [options.limit] How many rows to delete * @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 * @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) { Model.prototype.destroy = function(where, options) {
options = options || {} options = options || {}
...@@ -1339,7 +1339,7 @@ module.exports = (function() { ...@@ -1339,7 +1339,7 @@ module.exports = (function() {
* @param {Number} [options.limit] How many rows to update (only for mysql and mariadb) * @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 * @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) { Model.prototype.update = function(attrValueHash, where, options) {
var self = this var self = this
...@@ -1432,7 +1432,7 @@ module.exports = (function() { ...@@ -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. * 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) { Model.prototype.describe = function(schema) {
return this.QueryInterface.describeTable(this.tableName, schema || this.options.schema || undefined) return this.QueryInterface.describeTable(this.tableName, schema || this.options.schema || undefined)
......
...@@ -552,6 +552,8 @@ module.exports = (function() { ...@@ -552,6 +552,8 @@ module.exports = (function() {
}) })
} }
options.lock = queryOptions.lock
var sql = this.QueryGenerator.selectQuery(tableName, options, factory) var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { queryOptions = Utils._.extend({}, queryOptions, {
include: options.include, include: options.include,
......
...@@ -40,6 +40,26 @@ Transaction.ISOLATION_LEVELS = { ...@@ -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 * Commit the transaction
* *
* @return {this} * @return {this}
......
...@@ -998,7 +998,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -998,7 +998,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.sequelize.sync({ force: true }).success(function() { this.sequelize.sync({ force: true }).success(function() {
Task.create({ title: 'task' }).success(function(task) { 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) { task.getUsers().success(function(_users) {
expect(_users).to.have.length(1) expect(_users).to.have.length(1)
......
...@@ -57,7 +57,41 @@ describe(Support.getTestDialectTeaser("Self"), function() { ...@@ -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 Person = this.sequelize.define('Person', { name: DataTypes.STRING });
var Family = this.sequelize.define('Family', { var Family = this.sequelize.define('Family', {
preexisting_child: { preexisting_child: {
......
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, dialect = Support.getTestDialect()
, Transaction = require(__dirname + '/../lib/transaction') , Transaction = require(__dirname + '/../lib/transaction')
, sinon = require('sinon')
describe(Support.getTestDialectTeaser("Transaction"), function () { describe(Support.getTestDialectTeaser("Transaction"), function () {
this.timeout(4000); this.timeout(4000);
...@@ -71,4 +74,122 @@ describe(Support.getTestDialectTeaser("Transaction"), function () { ...@@ -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!