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

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.through.tableName
} else {
this.associationAccessor = this.combinedTableName 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,14 +98,13 @@ module.exports = (function() { ...@@ -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 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") {
this.through = this.sequelize.define(this.through, {}, _.extend(this.options, { this.through = this.sequelize.define(this.through, {}, _.extend(this.options, {
...@@ -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)) { if (doubleLinked) {
delete this.source.rawAttributes[this.foreignIdentifier] 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 // 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.
......
...@@ -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 || {})
...@@ -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!