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

Commit 9135d344 by Jan Aagaard Meier

Merge pull request #1708 from sequelize/bulkCreatePromisify

Bulk create promisify
2 parents cc197a35 ea4fa212
...@@ -32,3 +32,14 @@ branches: ...@@ -32,3 +32,14 @@ branches:
cache: cache:
directories: directories:
- node_modules - node_modules
matrix:
fast_finish: true
include:
- node_js: "0.11"
env: DB=mysql DIALECT=mysql
allow_failures:
- node_js: "0.11"
env: DB=mysql DIALECT=mysql
\ No newline at end of file
...@@ -960,6 +960,7 @@ module.exports = (function() { ...@@ -960,6 +960,7 @@ module.exports = (function() {
* @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}
* @return {EventEmitter}
* @method * @method
* @alias findOrBuild * @alias findOrBuild
*/ */
...@@ -1044,14 +1045,15 @@ module.exports = (function() { ...@@ -1044,14 +1045,15 @@ module.exports = (function() {
/** /**
* Create and insert multiple instances in bulk. * Create and insert multiple instances in bulk.
* *
* The success handler is not passed any arguments. To obtain DAOs for the newly created values, you will need to query for them again. * The success handler is passed an array of instances, but please notice that these may not completely represent the state of the rows in the DB. This is because MySQL
* This is because MySQL and SQLite do not make it easy to obtain back automatically generated IDs and other default values in a way that can be mapped to multiple records. * and SQLite do not make it easy to obtain back automatically generated IDs and other default values in a way that can be mapped to multiple records.
* To obtain DAOs for the newly created values, you will need to query for them again.
* *
* @param {Array} records List of objects (key/value pairs) to create instances from * @param {Array} records List of objects (key/value pairs) to create instances from
* @param {Object} [options] * @param {Object} [options]
* @param {Array} [options.fields] Fields to insert (defaults to all fields) * @param {Array} [options.fields] Fields to insert (defaults to all fields)
* @param {Boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation * @param {Boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation
* @param {Boolean} [options.hooks=false] Run before / after bulkCreate hooks? * @param {Boolean} [options.hooks=false] Run before / after create hooks for each individual DAO? 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}
...@@ -1075,85 +1077,41 @@ module.exports = (function() { ...@@ -1075,85 +1077,41 @@ module.exports = (function() {
if (fieldsOrOptions instanceof Array) { if (fieldsOrOptions instanceof Array) {
options.fields = fieldsOrOptions options.fields = fieldsOrOptions
} else { } else {
options.fields = options.fields || [] options.fields = options.fields || Object.keys(this.attributes)
options = Utils._.extend(options, fieldsOrOptions) options = Utils._.extend(options, fieldsOrOptions)
} }
if(this.daoFactoryManager.sequelize.options.dialect === 'postgres' && options.ignoreDuplicates ) { if(this.sequelize.options.dialect === 'postgres' && options.ignoreDuplicates) {
return new Utils.Promise(function(resolve, reject) { return this.sequelize.Promise.reject(new Error('Postgres does not support the \'ignoreDuplicates\' option.'))
reject(new Error('Postgres does not support the \'ignoreDuplicates\' option.'))
});
} }
var self = this var self = this
, updatedAtAttr = this._timestampAttributes.updatedAt , updatedAtAttr = this._timestampAttributes.updatedAt
, createdAtAttr = this._timestampAttributes.createdAt , createdAtAttr = this._timestampAttributes.createdAt
, errors = [] , errors = []
, daoPromises = []
, daos = records.map(function(v) { , daos = records.map(function(v) {
return self.build(v, { return self.build(v, {
isNewRecord: true isNewRecord: true
}) })
}) })
return new Utils.CustomEventEmitter(function(emitter) { if (options.validate && options.fields.length) {
var done = function() { var skippedFields = Utils._.difference(Object.keys(self.attributes), options.fields);
self.runHooks('afterBulkCreate', daos, options.fields, function(err, newRecords, newFields) { }
if (!!err) {
return emitter.emit('error', err)
}
daos = newRecords || daos
options.fields = newFields || options.fields
emitter.emit('success', daos, options.fields) var runAfterCreate = function() {
}) return self.runHooks('afterBulkCreate', daos, options.fields).spread(function(newRecords) {
} return new self.sequelize.Promise.resolve(newRecords || daos)
})
}
var next = function(err) { return self.runHooks('beforeBulkCreate', daos, options.fields).spread(function(newRecords, newFields) {
if (err !== undefined && err !== null) { daos = newRecords || daos
return emitter.emit('error', err) options.fields = newFields || options.fields
}
var runHook = function (dao) {
if (options.hooks === false) { if (options.hooks === false) {
return runQuery()
}
var i = 0
var iterate = function(i) {
self.runHooks('beforeCreate', daos[i], function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
daos[i] = newValues || daos[i]
daos[i].save({ transaction: options.transaction }).error(function(err) {
emitter.emit('error', err)
}).success(function() {
self.runHooks('afterCreate', daos[i], function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
daos[i] = newValues || daos[i]
i++
if (i >= daos.length) {
return done()
}
iterate(i)
})
})
})
}
iterate(i)
}
var runQuery = function() {
// we will re-create from DAOs, which may have set up default attributes
records = []
daos.forEach(function(dao) {
var values = options.fields.length > 0 ? {} : dao.dataValues var values = options.fields.length > 0 ? {} : dao.dataValues
options.fields.forEach(function(field) { options.fields.forEach(function(field) {
...@@ -1169,65 +1127,50 @@ module.exports = (function() { ...@@ -1169,65 +1127,50 @@ module.exports = (function() {
} }
records.push(values) records.push(values)
})
self.QueryInterface.bulkInsert(self.getTableName(), records, options, self) return values
.on('sql', function(sql) { }
emitter.emit('sql', sql)
}) return self.runHooks('beforeCreate', dao).spread(function(newValues) {
.error(function(err) { dao = newValues || dao
emitter.emit('error', err) return dao.save({ transaction: options.transaction }).then(function() {
}).success(function(rows) { return self.runHooks('afterCreate', dao)
done() })
}) })
} }
self.runHooks('beforeBulkCreate', daos, options.fields, function(err, newRecords, newFields) { var runValidation = function (dao) {
if (!!err) { if (options.validate === false) {
return emitter.emit('error', err) return dao
} }
daos = newRecords || daos
options.fields = newFields || options.fields
if (options.validate === true) { var fn = options.hooks === true ? 'hookValidate' : 'validate'
if (options.fields.length) { return dao[fn]({skip: skippedFields}).then(function (err) {
var skippedFields = Utils._.difference(Object.keys(self.attributes), options.fields); if (!!err) {
errors.push({record: dao, errors: err})
} }
})
}
if (options.hooks === true) { records = []
var iterate = function(i) { daos.forEach(function (dao) {
daos[i].hookValidate({skip: skippedFields}).complete(function (err) { daoPromises.push(runValidation(dao))
if (!!err) { daoPromises.push(runHook(dao))
errors.push({record: v, errors: err}) })
}
i++
if (i > daos.length) {
next(errors.length > 0 ? errors : null)
}
iterate(i)
})
}
} else {
var afterDaos = Utils._.after(daos.length, function() {
next(errors.length > 0 ? errors : null)
})
daos.forEach(function(v) { return self.sequelize.Promise.all(daoPromises).then(function() {
v.validate({skip: skippedFields}).success(function(err) { if (errors.length) {
if (!!err) { // Validation or hooks failed
errors.push({record: v, errors: err}) return self.sequelize.Promise.reject(errors)
} } else if (records.length) {
afterDaos() // Insert all records at once
}) return self.QueryInterface.bulkInsert(self.getTableName(), records, options, self).then(runAfterCreate)
})
}
} else { } else {
next() // Records were already saved while running create / update hooks
return runAfterCreate()
} }
}) })
}).run() })
} }
/** /**
......
var Validator = require("validator") var Validator = require("validator")
, Utils = require("./utils") , Utils = require("./utils")
, sequelizeError = require("./errors") , sequelizeError = require("./errors")
, Promise = require("bluebird") , Promise = require("./promise")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, _ = require('lodash') , _ = require('lodash')
...@@ -141,18 +141,18 @@ DaoValidator.prototype.validate = function() { ...@@ -141,18 +141,18 @@ DaoValidator.prototype.validate = function() {
this.errors = new sequelizeError.ValidationError('Validation error') this.errors = new sequelizeError.ValidationError('Validation error')
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return Promise.settle([
Promise.settle([ self._builtinValidators(),
self._builtinValidators(), self._customValidators(),
self._customValidators(), ]).then(function () {
]).then(function () { if (Object.keys(self.errors).length) {
if (Object.keys(self.errors).length) { return self.errors
emitter.emit('success', self.errors) }
} else {
emitter.emit('success') return new Promise(function (resolve) {
} resolve()
}) })
}).run() })
} }
/** /**
...@@ -161,11 +161,10 @@ DaoValidator.prototype.validate = function() { ...@@ -161,11 +161,10 @@ DaoValidator.prototype.validate = function() {
* - Validation * - Validation
* - After Validation Model Hooks * - After Validation Model Hooks
* *
* @return {EventEmitter} * @return {Promise}
*/ */
DaoValidator.prototype.hookValidate = function() { DaoValidator.prototype.hookValidate = function() {
var self = this var self = this
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance).then(function () { return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance).then(function () {
return self.validate().then(function (error) { return self.validate().then(function (error) {
if (error) { if (error) {
...@@ -173,7 +172,7 @@ DaoValidator.prototype.hookValidate = function() { ...@@ -173,7 +172,7 @@ DaoValidator.prototype.hookValidate = function() {
} }
}); });
}).then(function () { }).then(function () {
return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance); return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance)
}).return(self.modelInstance); }).return(self.modelInstance);
} }
...@@ -296,8 +295,6 @@ DaoValidator.prototype._invokeCustomValidator = Promise.method(function(validato ...@@ -296,8 +295,6 @@ DaoValidator.prototype._invokeCustomValidator = Promise.method(function(validato
isAsync = true; isAsync = true;
} }
if (isAsync) { if (isAsync) {
if (optAttrDefined) { if (optAttrDefined) {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs)) validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs))
...@@ -347,7 +344,6 @@ DaoValidator.prototype._invokeBuiltinValidator = Promise.method(function(value, ...@@ -347,7 +344,6 @@ DaoValidator.prototype._invokeBuiltinValidator = Promise.method(function(value,
} }
}); });
/** /**
* Will validate a single field against its schema definition (isnull). * Will validate a single field against its schema definition (isnull).
* *
......
...@@ -64,111 +64,109 @@ module.exports = (function() { ...@@ -64,111 +64,109 @@ module.exports = (function() {
ConnectorManager.prototype.connect = function(callback) { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new Utils.Promise();
// in case database is slow to connect, prevent orphaning the client return new Utils.Promise(function (resolve, reject) {
// TODO: We really need some sort of queue/flush/drain mechanism // in case database is slow to connect, prevent orphaning the client
if (this.isConnecting && !this.pooling && this.client === null) { // TODO: We really need some sort of queue/flush/drain mechanism
emitter.emit('success', null) if (this.isConnecting && !this.pooling && this.client === null) {
return emitter return resolve()
} }
this.isConnecting = true
this.isConnected = false
var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config) this.isConnecting = true
, config = new this.ConnectionParameters(uri) this.isConnected = false
// set pooling parameters if specified var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config)
if (this.pooling) { , config = new this.ConnectionParameters(uri)
config.poolSize = this.config.pool.maxConnections || 10
config.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
config.reapIntervalMillis = this.config.pool.reapInterval || 1000
config.uuid = this.config.uuid
}
var connectCallback = function(err, client, done) { // set pooling parameters if specified
self.isConnecting = false if (this.pooling) {
config.poolSize = this.config.pool.maxConnections || 10
if (!!err) { config.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
// release the pool immediately, very important. config.reapIntervalMillis = this.config.pool.reapInterval || 1000
done && done(err) config.uuid = this.config.uuid
self.client = null }
var connectCallback = function(err, client, done) {
if (err.code) {
switch(err.code) {
case 'ECONNREFUSED':
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
emitter.emit('error', err)
break
}
} else {
emitter.emit('error', new Error(err.message))
}
} else if (client) {
var timezoneCallback = function() { var timezoneCallback = function() {
self.isConnected = true self.isConnected = true
self.client = client self.client = client
emitter.emit('success', done) resolve(done)
} }
if (self.config.keepDefaultTimezone) { self.isConnecting = false
Utils.tick(timezoneCallback)
if (!!err) {
// release the pool immediately, very important.
done && done(err)
self.client = null
if (err.code) {
switch(err.code) {
case 'ECONNREFUSED':
reject(new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
reject(new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
reject(err)
break
}
} else {
reject(new Error(err.message))
}
} else if (client) {
if (self.config.keepDefaultTimezone) {
timezoneCallback()
} else {
self.setTimezone(client, 'UTC', timezoneCallback)
}
} else if (self.config.native) {
if (self.config.keepDefaultTimezone) {
timezoneCallback()
} else {
self.setTimezone(self.client, 'UTC', timezoneCallback)
}
} else { } else {
self.setTimezone(client, 'UTC', timezoneCallback) done && done()
self.client = null
resolve()
} }
} else if (self.config.native) {
self.setTimezone(self.client, 'UTC', function() {
self.isConnected = true
emitter.emit('success', done)
})
} else {
done && done()
self.client = null
emitter.emit('success')
} }
}
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
this.pg.connect(config, connectCallback) this.pg.connect(config, connectCallback)
} else {
if (!!this.client) {
connectCallback(null, this.client)
} else { } else {
//create one-off client if (!!this.client) {
connectCallback(null, this.client)
var responded = false } else {
//create one-off client
this.client = new this.pg.Client(config)
this.client.connect(function(err, client, done) { var responded = false
responded = true
connectCallback(err, client || self.client, done) this.client = new this.pg.Client(config)
}) this.client.connect(function(err, client, done) {
responded = true
// If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted connectCallback(err, client || self.client, done)
this.client.on('end', function () { })
if (!responded) {
connectCallback(new Error('Connection timed out')) // If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
} this.client.on('end', function () {
}) if (!responded) {
connectCallback(new Error('Connection timed out'))
// Closes a client correctly even if we have backed up queries }
// https://github.com/brianc/node-postgres/pull/346 })
this.client.on('drain', function() {
self.clientDrained = true // Closes a client correctly even if we have backed up queries
}) // https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', function() {
self.clientDrained = true
})
}
} }
} }.bind(this))
return emitter
} }
ConnectorManager.prototype.setTimezone = function(client, timezone, callback) { ConnectorManager.prototype.setTimezone = function(client, timezone, callback) {
......
...@@ -61,7 +61,8 @@ Hooks.runHooks = function() { ...@@ -61,7 +61,8 @@ Hooks.runHooks = function() {
, hooks = arguments[0] , hooks = arguments[0]
, lastIndex = arguments.length-1 , lastIndex = arguments.length-1
, fn = typeof arguments[lastIndex] === "function" ? arguments[lastIndex] : null , fn = typeof arguments[lastIndex] === "function" ? arguments[lastIndex] : null
, args = Array.prototype.slice.call(arguments, 1, fn ? lastIndex : arguments.length) , fnArgs = Array.prototype.slice.call(arguments, 1, fn ? lastIndex : arguments.length)
, resolveArgs = fnArgs
if (typeof hooks === "string") { if (typeof hooks === "string") {
hooks = this.options.hooks[hooks] || [] hooks = this.options.hooks[hooks] || []
...@@ -71,38 +72,42 @@ Hooks.runHooks = function() { ...@@ -71,38 +72,42 @@ Hooks.runHooks = function() {
hooks = hooks === undefined ? [] : [hooks] hooks = hooks === undefined ? [] : [hooks]
} }
return new Promise(function (resolve, reject) { var promise = new Promise(function (resolve, reject) {
if (hooks.length < 1) { if (hooks.length < 1) {
return resolve(args) return resolve(fnArgs)
} }
var run = function(hook) { var run = function(hook) {
if (!hook) { if (!hook) {
return resolve(args) return resolve(resolveArgs)
} }
if (typeof hook === "object") { if (typeof hook === "object") {
hook = hook.fn hook = hook.fn
} }
hook.apply(self, args.concat(function() { hook.apply(self, fnArgs.concat(function() {
tick++ tick++
if (!!arguments[0]) { if (!!arguments[0]) {
return reject(arguments[0]) return reject(arguments[0])
} }
resolveArgs = Array.prototype.slice.call(arguments, 1)
// daoValues = newValues
return run(hooks[tick]) return run(hooks[tick])
})) }))
} }
run(hooks[tick]) run(hooks[tick])
}).spread(function () { })
if (fn) {
if (fn) {
promise.spread(function () {
fn.apply(self, [null].concat(Array.prototype.slice.apply(arguments))); fn.apply(self, [null].concat(Array.prototype.slice.apply(arguments)));
} }, fn);
}, fn); }
return promise
} }
Hooks.hook = function() { Hooks.hook = function() {
......
...@@ -3,9 +3,7 @@ var util = require("util") ...@@ -3,9 +3,7 @@ var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('./utils') , Utils = require('./utils')
, INTERNAL = function() {}
, async = require("bluebird/js/main/async.js")
/** /**
* A slightly modified version of bluebird promises. This means that, on top of the methods below, you can also call all the methods listed on the link below. * A slightly modified version of bluebird promises. This means that, on top of the methods below, you can also call all the methods listed on the link below.
* *
...@@ -14,115 +12,7 @@ var util = require("util") ...@@ -14,115 +12,7 @@ var util = require("util")
* @mixes https://github.com/petkaantonov/bluebird/blob/master/API.md * @mixes https://github.com/petkaantonov/bluebird/blob/master/API.md
* @class Promise * @class Promise
*/ */
var SequelizePromise = function(resolver) { var SequelizePromise = Promise = require('bluebird')
var self = this;
// Copied from Bluebird, bluebird doesn't like Promise.call(this)
// mhansen wrote and is no fan of this, but sees no other way of making Promises first class while preserving SQL logging capabilities and BC.
this._bitField = 0;
this._fulfillmentHandler0 = void 0;
this._rejectionHandler0 = void 0;
this._promise0 = void 0;
this._receiver0 = void 0;
this._settledValue = void 0;
this._boundTo = void 0;
// Intercept the resolver so we can resolve with emit's
this._resolveFromResolver(function resolverIntercept(resolve, reject) {
self.seqResolve = resolve;
self.seqReject = reject;
if (resolver) {
resolver.apply(this, arguments);
}
}.bind(this));
// Sequelize speific
this.$sql = [];
};
Promise = require("bluebird")
util.inherits(SequelizePromise, Promise)
Utils._.extend(SequelizePromise, Promise)
SequelizePromise.all = function(promises) {
var resolved = SequelizePromise.resolve(Promise.all(promises));
// Propagate sql events
promises.forEach(function (promise) {
promise.on('sql', function (sql) {
resolved.emit('sql', sql);
});
promise.$sql.forEach(function (sql) {
resolved.emit('sql', sql);
});
});
return resolved;
};
// Need to hack resolve cause we can't hack all directrly
SequelizePromise.resolve = SequelizePromise.fulfilled = function(value) {
var ret = new SequelizePromise(INTERNAL);
if (ret._tryFollow(value)) {
return ret;
}
ret._cleanValues();
ret._setFulfilled();
ret._settledValue = value;
return ret;
};
// Need to hack _then to make sure our promise is chainable
SequelizePromise.prototype._then = function (
didFulfill,
didReject,
didProgress,
receiver,
internalData
) {
var haveInternalData = internalData !== void 0;
var ret = haveInternalData ? internalData : new SequelizePromise(INTERNAL); // The relevant line, rest is fine
if (!haveInternalData && this._isBound()) {
ret._setBoundTo(this._boundTo);
}
// Start of sequelize specific
// Needed to transfer sql events accross .then() calls
if (this.proxySql && ret && ret.emit) {
this.proxySql(ret);
}
// End of sequelize specific
var callbackIndex = this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
if (!haveInternalData && this._cancellable()) {
ret._setCancellable();
ret._cancellationParent = this;
}
if (this.isResolved()) {
async.invoke(this._queueSettleAt, this, callbackIndex);
}
return ret;
};
SequelizePromise.prototype._settlePromiseAt = function (index) {
var receiver = this._receiverAt(index);
if (this.$sql && receiver && receiver.emit) {
this.$sql.forEach(function (sql) {
receiver.emit('sql', sql);
});
}
return Promise.prototype._settlePromiseAt.apply(this, arguments);
};
/** /**
* Listen for events, event emitter style. Mostly for backwards compat. with EventEmitter * Listen for events, event emitter style. Mostly for backwards compat. with EventEmitter
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"generic-pool": "2.0.4", "generic-pool": "2.0.4",
"sql": "~0.35.0", "sql": "~0.35.0",
"circular-json": "~0.1.5", "circular-json": "~0.1.5",
"bluebird": "~1.0.0", "bluebird": "git://github.com/sequelize/bluebird.git",
"node-uuid": "~1.4.1" "node-uuid": "~1.4.1"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -945,7 +945,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -945,7 +945,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
self.Task.create({ id: 15, title: 'task2' }).success(function(task2) { self.Task.create({ id: 15, title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) { user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) {
var tickChar = (Support.getTestDialect() === 'postgres') ? '"' : '`' var tickChar = (Support.getTestDialect() === 'postgres') ? '"' : '`'
expect(sql).to.have.string("INSERT INTO %TasksUsers% (%UserId%,%TaskId%) VALUES (1,12),(1,15)".replace(/%/g, tickChar)) expect(sql).to.have.string("INSERT INTO %TasksUsers% (%TaskId%,%UserId%) VALUES (12,1),(15,1)".replace(/%/g, tickChar))
})).success(function () { })).success(function () {
expect(spy.calledTwice).to.be.ok // Once for SELECT, once for INSERT expect(spy.calledTwice).to.be.ok // Once for SELECT, once for INSERT
done() done()
......
...@@ -480,7 +480,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -480,7 +480,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(str2.str).to.equal('http://sequelizejs.org') expect(str2.str).to.equal('http://sequelizejs.org')
StringIsNullOrUrl.create({ str: '' }).error(function(err) { StringIsNullOrUrl.create({ str: '' }).error(function(err) {
expect(err).to.exist expect(err).to.exist
expect(err.str[0].message).to.match(/Validation isURL failed/) expect(err.str[0].message).to.match(/Validation isURL failed/)
done() done()
}) })
......
...@@ -219,8 +219,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -219,8 +219,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
} }
}) })
var successfulUser = UserSuccess.build({ name: succeedingValue }) var successfulUser = UserSuccess.build({ name: succeedingValue })
successfulUser.validate().success( function() { successfulUser.validate().success(function(errors) {
expect(arguments).to.have.length(0) expect(errors).to.be.undefined
done() done()
}).error(function(err) { }).error(function(err) {
expect(err).to.deep.equal({}) expect(err).to.deep.equal({})
...@@ -539,11 +539,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -539,11 +539,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
expect(error.name[0].message).to.equal("name should equal '2'") expect(error.name[0].message).to.equal("name should equal '2'")
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
successfulUser.validate().success(function() { successfulUser.validate().success(function(err) {
expect(arguments).to.have.length(0) expect(err).not.to.be.defined
done()
}).error(function(err) {
expect(err[0].message).to.equal()
done() done()
}) })
}) })
...@@ -572,8 +569,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -572,8 +569,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
expect(error).to.be.an.instanceOf(self.sequelize.ValidationError) expect(error).to.be.an.instanceOf(self.sequelize.ValidationError)
expect(error.name[0].message).to.equal("Invalid username") expect(error.name[0].message).to.equal("Invalid username")
User.build({ name : "no error" }).validate().success(function() { User.build({ name : "no error" }).validate().success(function(errors) {
expect(arguments).to.have.length(0) expect(errors).not.to.be.defined
done() done()
}) })
}) })
......
...@@ -119,6 +119,24 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -119,6 +119,24 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}) })
}) })
}) })
it('can modify fields', function () {
var self = this
this.User.beforeBulkCreate(function (daos, fields, fn) {
fn(null, daos, ['username'])
})
return this.User.bulkCreate([
{username: 'Bob', mood: 'cold'},
{username: 'Tobi', mood: 'hot'}
], { fields: [], hooks: false }).success(function(bulkUsers) {
return self.User.all().success(function(users) {
expect(users[0].mood).to.equal(null)
expect(users[1].mood).to.equal(null)
})
})
})
}) })
it('#create', function(done) { it('#create', function(done) {
...@@ -464,7 +482,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -464,7 +482,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on user', function(done) { it('should return an error based on user', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -501,7 +519,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -501,7 +519,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on the hook', function(done) { it('should return an error based on the hook', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ) expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done() done()
}) })
}) })
...@@ -552,7 +570,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -552,7 +570,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on user', function(done) { it('should return an error based on user', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ) expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done() done()
}) })
}) })
...@@ -698,7 +716,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -698,7 +716,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return the user from the callback', function(done) { it('should return the user from the callback', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ) expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done() done()
}) })
}) })
...@@ -720,7 +738,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -720,7 +738,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return the error without the user within callback', function(done) { it('should return the error without the user within callback', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ) expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done() done()
}) })
}) })
...@@ -835,7 +853,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -835,7 +853,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) { it('#create', function(done) {
this.User.create({mood: 'creative'}).error(function(err) { this.User.create({mood: 'creative'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -859,7 +877,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -859,7 +877,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) { it('#create', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -940,7 +958,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -940,7 +958,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -954,7 +972,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -954,7 +972,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -1061,7 +1079,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -1061,7 +1079,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) { it('#create', function(done) {
this.User.create({mood: 'creative'}).error(function(err) { this.User.create({mood: 'creative'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
...@@ -1085,7 +1103,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -1085,7 +1103,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) { it('#create', function(done) {
this.User.create({mood: 'happy'}).error(function(err) { this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error); expect(err).to.be.instanceOf(Error);
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral') expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done() done()
}) })
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!