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

Commit 4c1e14b5 by Mick Hansen

Merge pull request #1663 from sequelize/feat-promise-as-first-class

Promises as first class citizens
2 parents 530332ae 24b23626
...@@ -86,36 +86,22 @@ module.exports = (function() { ...@@ -86,36 +86,22 @@ module.exports = (function() {
instancePrototype[this.accessors.set] = function(associatedInstance, options) { instancePrototype[this.accessors.set] = function(associatedInstance, options) {
var instance = this var instance = this
return new Utils.CustomEventEmitter(function(emitter) { return instance[association.accessors.get](options).then(function(oldInstance) {
instance[association.accessors.get](options)
.success(function(oldInstance) {
if (oldInstance) { if (oldInstance) {
oldInstance[association.identifier] = null oldInstance[association.identifier] = null
oldInstance return oldInstance.save(Utils._.extend({}, options, {
.save(Utils._.extend({}, options, {
fields: [association.identifier], fields: [association.identifier],
allowNull: [association.identifier], allowNull: [association.identifier],
association: true association: true
})) }))
.success(function() {
if (associatedInstance) {
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
associatedInstance.save(options).proxy(emitter)
} else {
emitter.emit('success', null)
} }
}) }).then(function () {
} else {
if (associatedInstance) { if (associatedInstance) {
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier)) associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
associatedInstance.save(options).proxy(emitter) return associatedInstance.save(options)
} else {
emitter.emit('success', null)
}
} }
return null;
}) })
.proxy(emitter, { events: ['sql'] })
}).run()
} }
return this return this
......
...@@ -336,25 +336,17 @@ module.exports = (function() { ...@@ -336,25 +336,17 @@ module.exports = (function() {
options = Utils._.extend({}, this.options, options || {}) options = Utils._.extend({}, this.options, options || {})
var self = this var self = this
, doQuery = function() {
return new Utils.CustomEventEmitter(function(emitter) { return self.QueryInterface.createTable(self.getTableName(), self.attributes, options);
var doQuery = function() {
self
.QueryInterface
.createTable(self.getTableName(), self.attributes, options)
.proxy(emitter, {events: ['error', 'sql']})
.success(function() { emitter.emit('success', self) })
} }
if (options.force) { if (options.force) {
self return self.drop(options).then(function () {
.drop(options) return doQuery().return(self);
.proxy(emitter, {events: ['error', 'sql']}) });
.success(doQuery)
} else { } else {
doQuery() return doQuery().return(this)
} }
}).run()
} }
/** /**
...@@ -1224,7 +1216,6 @@ module.exports = (function() { ...@@ -1224,7 +1216,6 @@ module.exports = (function() {
if (!!err) { if (!!err) {
return emitter.emit('error', err) return emitter.emit('error', err)
} }
daos = newRecords || daos daos = newRecords || daos
options.fields = newFields || options.fields options.fields = newFields || options.fields
......
...@@ -166,27 +166,15 @@ DaoValidator.prototype.validate = function() { ...@@ -166,27 +166,15 @@ DaoValidator.prototype.validate = function() {
DaoValidator.prototype.hookValidate = function() { DaoValidator.prototype.hookValidate = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance).then(function () {
self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance, function(err) { return self.validate().then(function (error) {
if (!!err) { if (error) {
return emitter.emit('error', err) throw error
}
self.validate().success(function (error) {
if (!!error) {
return emitter.emit('error', error)
} }
});
self.modelInstance.Model.runHooks('afterValidate', self.modelInstance, function(err) { }).then(function () {
if (!!err) { return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance);
return emitter.emit('error', err) }).return(self.modelInstance);
}
emitter.emit('success', self.modelInstance)
})
})
})
}).run()
} }
/** /**
......
...@@ -436,10 +436,9 @@ module.exports = (function() { ...@@ -436,10 +436,9 @@ module.exports = (function() {
} }
} }
return new Utils.CustomEventEmitter(function(emitter) { return self.hookValidate({
self.hookValidate({
skip: _.difference(Object.keys(self.rawAttributes), options.fields) skip: _.difference(Object.keys(self.rawAttributes), options.fields)
}).proxy(emitter, { events: ['error'] }).success(function() { }).then(function () {
options.fields.forEach(function(field) { options.fields.forEach(function(field) {
if (self.dataValues[field] !== undefined) { if (self.dataValues[field] !== undefined) {
values[field] = self.dataValues[field] values[field] = self.dataValues[field]
...@@ -511,12 +510,7 @@ module.exports = (function() { ...@@ -511,12 +510,7 @@ module.exports = (function() {
// Add the values to the DAO // Add the values to the DAO
self.dataValues = _.extend(self.dataValues, values) self.dataValues = _.extend(self.dataValues, values)
// Run the beforeCreate / beforeUpdate hook return self.Model.runHooks('before' + hook, self).then(function () {
self.Model.runHooks('before' + hook, self, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
// dataValues might have changed inside the hook, rebuild // dataValues might have changed inside the hook, rebuild
// the values hash // the values hash
values = {} values = {}
...@@ -528,15 +522,13 @@ module.exports = (function() { ...@@ -528,15 +522,13 @@ module.exports = (function() {
}) })
args[2] = values args[2] = values
self.QueryInterface[query].apply(self.QueryInterface, args) return self.QueryInterface[query].apply(self.QueryInterface, args).catch(function(err) {
.proxy(emitter, {events: ['sql']})
.error(function(err) {
if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) { if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) {
var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString()) var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString())
if (fields !== false) { if (fields !== false) {
fields = fields.filter(function(f) { return f !== self.Model.tableName; }) fields = fields.filter(function(f) { return f !== self.Model.tableName; })
Utils._.each(self.__options.uniqueKeys, function(value, key) { Utils._.each(self.__options.uniqueKeys, function(value) {
if (Utils._.isEqual(value.fields, fields) && !!value.msg) { if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
err = new Error(value.msg) err = new Error(value.msg)
} }
...@@ -544,9 +536,8 @@ module.exports = (function() { ...@@ -544,9 +536,8 @@ module.exports = (function() {
} }
} }
emitter.emit('error', err) throw err
}) }).then(function(result) {
.success(function(result) {
// Transfer database generated values (defaults, autoincrement, etc) // Transfer database generated values (defaults, autoincrement, etc)
values = _.extend(values, result.dataValues) values = _.extend(values, result.dataValues)
...@@ -554,16 +545,10 @@ module.exports = (function() { ...@@ -554,16 +545,10 @@ module.exports = (function() {
result.dataValues = _.extend(result.dataValues, values) result.dataValues = _.extend(result.dataValues, values)
result._previousDataValues = _.clone(result.dataValues) result._previousDataValues = _.clone(result.dataValues)
self.Model.runHooks('after' + hook, result, function(err) { return self.Model.runHooks('after' + hook, result).return(result)
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', result)
})
}) })
}) })
}) })
}).run()
} }
/* /*
...@@ -654,39 +639,23 @@ module.exports = (function() { ...@@ -654,39 +639,23 @@ module.exports = (function() {
options.force = options.force === undefined ? false : Boolean(options.force) options.force = options.force === undefined ? false : Boolean(options.force)
var self = this var self = this
, query = null
return new Utils.CustomEventEmitter(function(emitter) { // This semi awkward syntax where we can't return the chain directly but have to return the last .then() call is to allow sql proxying
self.Model.runHooks(self.Model.options.hooks.beforeDestroy, self, function(err) { return self.Model.runHooks(self.Model.options.hooks.beforeDestroy, self).then(function () {
if (!!err) { var query
return emitter.emit('error', err) , identifier
}
if (self.Model._timestampAttributes.deletedAt && options.force === false) { if (self.Model._timestampAttributes.deletedAt && options.force === false) {
self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date() self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date()
query = self.save(options) query = self.save(options)
} else { } else {
var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id }; identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.Model), identifier, options) query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.Model), identifier, options)
} }
return query;
query.on('sql', function(sql) { }).then(function (results) {
emitter.emit('sql', sql) return self.Model.runHooks(self.Model.options.hooks.afterDestroy, self).return(results);
}) });
.error(function(err) {
emitter.emit('error', err)
})
.success(function(results) {
self.Model.runHooks(self.Model.options.hooks.afterDestroy, self, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', results)
})
})
})
}).run()
} }
/** /**
......
var Utils = require('../../utils') var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter") , CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Promise = require("../../promise")
, Dot = require('dottie') , Dot = require('dottie')
, _ = require('lodash') , _ = require('lodash')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
......
...@@ -47,21 +47,15 @@ module.exports = (function() { ...@@ -47,21 +47,15 @@ module.exports = (function() {
self.pendingQueries++ self.pendingQueries++
self.clientDrained = false self.clientDrained = false
return new Utils.CustomEventEmitter(function(emitter) { return self.connect().then(function(done) {
self.connect()
.on('error', function(err) {
emitter.emit('error', err)
})
.on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {}) var query = new Query(self.client, self.sequelize, callee, options || {})
return query.run(sql) // We return the query regardless of error or success in the query
.complete(function(err) { return query.run(sql).finally(function () {
self.endQuery.call(self) self.endQuery.call(self);
done && done(err) }) done && done();
.proxy(emitter) });
}) });
}).run()
} }
ConnectorManager.prototype.afterTransactionSetup = function(callback) { ConnectorManager.prototype.afterTransactionSetup = function(callback) {
...@@ -70,7 +64,7 @@ module.exports = (function() { ...@@ -70,7 +64,7 @@ module.exports = (function() {
ConnectorManager.prototype.connect = function(callback) { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new (require('events').EventEmitter)() var emitter = new Utils.Promise();
// in case database is slow to connect, prevent orphaning the client // in case database is slow to connect, prevent orphaning the client
// TODO: We really need some sort of queue/flush/drain mechanism // TODO: We really need some sort of queue/flush/drain mechanism
......
...@@ -3,6 +3,7 @@ var Utils = require("../../utils") ...@@ -3,6 +3,7 @@ var Utils = require("../../utils")
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, hstore = require('./hstore') , hstore = require('./hstore')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, Promise = require('../../promise')
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
...@@ -31,6 +32,9 @@ module.exports = (function() { ...@@ -31,6 +32,9 @@ module.exports = (function() {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql) this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql)
} }
return new Promise(function (resolve, reject) {
var promise = this;
query.on('row', function(row) { query.on('row', function(row) {
rows.push(row) rows.push(row)
}) })
...@@ -38,30 +42,20 @@ module.exports = (function() { ...@@ -38,30 +42,20 @@ module.exports = (function() {
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
err.sql = sql err.sql = sql
self.emit('sql', sql, self.options.uuid) promise.emit('sql', sql, self.options.uuid)
self.emit('error', err, self.callee) reject(err);
}) })
query.on('end', function(result) { query.on('end', function(result) {
self.emit('sql', self.sql, self.options.uuid)
if (receivedError) { if (receivedError) {
return return;
} }
onSuccess.call(self, rows, sql, result) promise.emit('sql', self.sql, self.options.uuid)
resolve([rows, sql, result]);
}) })
}).spread(function (rows, sql, result) {
return this
}
Query.prototype.getInsertIdField = function() {
return 'id'
}
var onSuccess = function(rows, sql, result) {
var results = rows var results = rows
, self = this
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
...@@ -76,12 +70,12 @@ module.exports = (function() { ...@@ -76,12 +70,12 @@ module.exports = (function() {
} else { } else {
results = rows.map(function(row) { return Utils._.values(row) }) results = rows.map(function(row) { return Utils._.values(row) })
} }
return this.emit('success', results) return results
} }
if (this.send('isSelectQuery')) { if (self.send('isSelectQuery')) {
if (this.sql.toLowerCase().indexOf('select c.column_name') === 0) { if (self.sql.toLowerCase().indexOf('select c.column_name') === 0) {
var result = {} result = {}
rows.forEach(function(_result) { rows.forEach(function(_result) {
result[_result.Field] = { result[_result.Field] = {
...@@ -111,12 +105,12 @@ module.exports = (function() { ...@@ -111,12 +105,12 @@ module.exports = (function() {
} }
}) })
this.emit('success', result) return result
} else { } else {
// Postgres will treat tables as case-insensitive, so fix the case // Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes // of the returned values to match attributes
if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {}) var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) { rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) { Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key] var targetAttr = attrsMap[key]
...@@ -130,7 +124,7 @@ module.exports = (function() { ...@@ -130,7 +124,7 @@ module.exports = (function() {
// Parse hstore fields if the model has any hstore fields. // Parse hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT. // This cannot be done in the 'pg' lib because hstore is a UDT.
if (!!this.callee && !!this.callee._hasHstoreAttributes) { if (!!self.callee && !!self.callee._hasHstoreAttributes) {
rows.forEach(function(row) { rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) { Utils._.keys(row).forEach(function(key) {
if (self.callee._isHstoreAttribute(key)) { if (self.callee._isHstoreAttribute(key)) {
...@@ -140,29 +134,36 @@ module.exports = (function() { ...@@ -140,29 +134,36 @@ module.exports = (function() {
}) })
} }
this.emit('success', this.send('handleSelectQuery', rows)) return self.send('handleSelectQuery', rows)
} }
} else if (this.send('isShowOrDescribeQuery')) { } else if (self.send('isShowOrDescribeQuery')) {
this.emit('success', results) return results
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(this.options.type) !== -1) { } else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) {
this.emit('success', result.rowCount) return result.rowCount
} else if (this.send('isInsertQuery') || this.send('isUpdateQuery')) { } else if (self.send('isInsertQuery') || self.send('isUpdateQuery')) {
if (this.callee !== null) { // may happen for bulk inserts or bulk updates if (self.callee !== null) { // may happen for bulk inserts or bulk updates
for (var key in rows[0]) { for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key] var record = rows[0][key]
if (!!this.callee.Model && !!this.callee.Model.rawAttributes && !!this.callee.Model.rawAttributes[key] && !!this.callee.Model.rawAttributes[key].type && this.callee.Model.rawAttributes[key].type.toString() === DataTypes.HSTORE.toString()) { if (!!self.callee.Model && !!self.callee.Model.rawAttributes && !!self.callee.Model.rawAttributes[key] && !!self.callee.Model.rawAttributes[key].type && self.callee.Model.rawAttributes[key].type.toString() === DataTypes.HSTORE.toString()) {
record = hstore.parse(record) record = hstore.parse(record)
} }
this.callee.dataValues[key] = record self.callee.dataValues[key] = record
} }
} }
} }
this.emit('success', this.callee) return self.callee
} else { } else {
this.emit('success', results) return results
}
})
return this
} }
Query.prototype.getInsertIdField = function() {
return 'id'
} }
return Query return Query
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("bluebird") , Promise = require("../promise")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('../utils') , Utils = require('../utils')
...@@ -214,6 +214,12 @@ module.exports = (function() { ...@@ -214,6 +214,12 @@ module.exports = (function() {
return this return this
} }
CustomEventEmitter.prototype.proxySql = function(promise) {
return this.proxy(promise, {
events: ['sql']
})
}
/** /**
* Attach listeners to the emitter, promise style. * Attach listeners to the emitter, promise style.
* *
...@@ -227,10 +233,13 @@ module.exports = (function() { ...@@ -227,10 +233,13 @@ module.exports = (function() {
onFulfilled = bindToProcess(onFulfilled) onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected) onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) { var promise = (new Promise(function (resolve, reject) {
self.on('error', reject) self.on('error', reject)
.on('success', resolve) .on('success', resolve)
}).then(onFulfilled, onRejected) })).then(onFulfilled, onRejected)
this.proxySql(promise);
return promise;
} }
/** /**
...@@ -246,12 +255,15 @@ module.exports = (function() { ...@@ -246,12 +255,15 @@ module.exports = (function() {
onFulfilled = bindToProcess(onFulfilled) onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected) onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) { var promise = (new Promise(function (resolve, reject) {
self.on('error', reject) self.on('error', reject)
.on('success', function () { .on('success', function () {
resolve(Array.prototype.slice.apply(arguments)) // Transform args to an array resolve(Array.prototype.slice.apply(arguments)) // Transform args to an array
}) })
}).spread(onFulfilled, onRejected) })).spread(onFulfilled, onRejected)
this.proxySql(promise);
return promise;
} }
/** /**
......
var Utils = require("./utils") var Utils = require("./utils")
, Promise = require("./promise")
/** /**
* Hooks are function that are called before and after (bulk-) creation/updating/deletion and validation. Hooks can be added to you models in three ways: * Hooks are function that are called before and after (bulk-) creation/updating/deletion and validation. Hooks can be added to you models in three ways:
...@@ -58,8 +59,9 @@ Hooks.runHooks = function() { ...@@ -58,8 +59,9 @@ Hooks.runHooks = function() {
var self = this var self = this
, tick = 0 , tick = 0
, hooks = arguments[0] , hooks = arguments[0]
, args = Array.prototype.slice.call(arguments, 1, arguments.length-1) , lastIndex = arguments.length-1
, fn = arguments[arguments.length-1] , fn = typeof arguments[lastIndex] === "function" ? arguments[lastIndex] : null
, args = Array.prototype.slice.call(arguments, 1, fn ? lastIndex : arguments.length)
if (typeof hooks === "string") { if (typeof hooks === "string") {
hooks = this.options.hooks[hooks] || [] hooks = this.options.hooks[hooks] || []
...@@ -69,13 +71,14 @@ Hooks.runHooks = function() { ...@@ -69,13 +71,14 @@ Hooks.runHooks = function() {
hooks = hooks === undefined ? [] : [hooks] hooks = hooks === undefined ? [] : [hooks]
} }
return new Promise(function (resolve, reject) {
if (hooks.length < 1) { if (hooks.length < 1) {
return fn.apply(this, [null].concat(args)) return resolve(args)
} }
var run = function(hook) { var run = function(hook) {
if (!hook) { if (!hook) {
return fn.apply(this, [null].concat(args)) return resolve(args)
} }
if (typeof hook === "object") { if (typeof hook === "object") {
...@@ -86,7 +89,7 @@ Hooks.runHooks = function() { ...@@ -86,7 +89,7 @@ Hooks.runHooks = function() {
tick++ tick++
if (!!arguments[0]) { if (!!arguments[0]) {
return fn(arguments[0]) return reject(arguments[0])
} }
// daoValues = newValues // daoValues = newValues
...@@ -95,6 +98,11 @@ Hooks.runHooks = function() { ...@@ -95,6 +98,11 @@ Hooks.runHooks = function() {
} }
run(hooks[tick]) run(hooks[tick])
}).spread(function () {
if (fn) {
fn.apply(self, [null].concat(Array.prototype.slice.apply(arguments)));
}
}, fn);
} }
Hooks.hook = function() { Hooks.hook = function() {
......
var util = require("util")
, Promise
, EventEmitter = require("events").EventEmitter
, proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('./utils')
, INTERNAL = function() {}
, async = require("bluebird/js/main/async.js")
var SequelizePromise = function(resolver) {
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);
};
SequelizePromise.prototype.on = function(evt, fct) {
if (evt === 'success') {
this.then(fct);
}
else if (evt === 'error') {
this.then(null, fct);
}
else {
EventEmitter.prototype.on.call(this, evt, fct);
}
return this;
}
SequelizePromise.prototype.emit = function(evt) {
var args = arguments.length > 1 ? Array.prototype.slice.call(arguments, 1) : [];
if (evt === 'success') {
this.seqResolve.apply(this, args);
} else if (evt === 'error') {;
this.seqReject.apply(this, args);
} else {
// Needed to transfer sql across .then() calls
if (evt === 'sql') {
this.$sql.push(args[0]);
}
EventEmitter.prototype.emit.apply(this, [evt].concat(args));
}
return this;
};
/**
* Listen for success events.
*
* ```js
* promise.success(function (result) {
* //...
* });
* ```
*
* @param {function} onSuccess
* @method success
* @alias ok
* @return this
*/
SequelizePromise.prototype.success =
SequelizePromise.prototype.ok = function(fct) {
return this.then(fct);
}
/**
* Listen for error events
*
* ```js
* promise.error(function (err) {
* //...
* });
* ```
*
* @param {function} onError
* @metohd error
* @alias fail
* @alias failure
* @return this
*/
SequelizePromise.prototype.failure =
SequelizePromise.prototype.fail =
SequelizePromise.prototype.error = function(fct) {
return this.then(null, fct);
}
/**
* Listen for both success and error events.
*
* ```js
* promise.done(function (err, result) {
* //...
* });
* ```
*
* @param {function} onDone
* @method done
* @alias complete
* @return this
*/
SequelizePromise.prototype.done =
SequelizePromise.prototype.complete = function(fct) {
if (fct.length > 2) {
return this.spread(function () {
fct.apply(null, [null].concat(Array.prototype.slice.call(arguments)));
}, fct);
} else {
return this.then(function () {
fct.apply(null, [null].concat(Array.prototype.slice.call(arguments)));
}, fct);
}
};
/*
* Attach a function that is called every time the function that created this emitter executes a query.
* @param {function} onSQL
* @return this
*/
SequelizePromise.prototype.sql = function(fct) {
this.on('sql', fct)
return this;
}
/**
* Proxy every event of this promise to another one.
*
* @param {SequelizePromise} The promise that should receive the events.
* @param {Object} [options]
* @param {Array} [options.events] An array of the events to proxy. Defaults to sql, error and success
* @return this
*/
SequelizePromise.prototype.proxy = function(promise, options) {
options = Utils._.extend({
events: proxyEventKeys,
skipEvents: []
}, options || {})
options.events = Utils._.difference(options.events, options.skipEvents)
options.events.forEach(function (eventKey) {
this.on(eventKey, function () {
var args = [ eventKey ].concat([].slice.apply(arguments))
promise.emit.apply(promise, args)
})
}.bind(this))
return this
}
SequelizePromise.prototype.proxySql = function(promise) {
return this.proxy(promise, {
events: ['sql']
});
};
module.exports = SequelizePromise;
\ No newline at end of file
...@@ -2,6 +2,7 @@ var Utils = require(__dirname + '/utils') ...@@ -2,6 +2,7 @@ var Utils = require(__dirname + '/utils')
, DataTypes = require(__dirname + '/data-types') , DataTypes = require(__dirname + '/data-types')
, SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface') , SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface')
, Transaction = require(__dirname + '/transaction') , Transaction = require(__dirname + '/transaction')
, Promise = require(__dirname + '/promise')
, QueryTypes = require('./query-types') , QueryTypes = require('./query-types')
module.exports = (function() { module.exports = (function() {
...@@ -87,10 +88,9 @@ module.exports = (function() { ...@@ -87,10 +88,9 @@ module.exports = (function() {
logging: this.sequelize.options.logging logging: this.sequelize.options.logging
}, options || {}) }, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums // Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") { if (self.sequelize.options.dialect === "postgres") {
var chainer = new Utils.QueryChainer() var promises = []
// For backwards-compatibility, public schemas don't need to // For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type // explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName , getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
...@@ -98,12 +98,12 @@ module.exports = (function() { ...@@ -98,12 +98,12 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) { for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/) || attributes[keys[i]].toString() === "ENUM" || (attributes[keys[i]].type && attributes[keys[i]].type.toString() === "ENUM")) { if (attributes[keys[i]].toString().match(/^ENUM\(/) || attributes[keys[i]].toString() === "ENUM" || (attributes[keys[i]].type && attributes[keys[i]].type.toString() === "ENUM")) {
sql = self.QueryGenerator.pgListEnums(getTableName, keys[i], options) sql = self.QueryGenerator.pgListEnums(getTableName, keys[i], options)
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: QueryTypes.SELECT, logging: options.logging })) promises.push(self.sequelize.query(sql, null, { plain: true, raw: true, type: QueryTypes.SELECT, logging: options.logging }))
} }
} }
chainer.runSerially().success(function(results) { return Promise.all(promises).then(function(results) {
var chainer2 = new Utils.QueryChainer() var promises = []
// Find the table that we're trying to create throgh DAOFactoryManager // Find the table that we're trying to create throgh DAOFactoryManager
, daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) { return dao.tableName === tableName }) , daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) { return dao.tableName === tableName })
, enumIdx = 0 , enumIdx = 0
...@@ -115,7 +115,7 @@ module.exports = (function() { ...@@ -115,7 +115,7 @@ module.exports = (function() {
// If the enum type doesn't exist then create it // If the enum type doesn't exist then create it
if (!results[enumIdx]) { if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options) sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options)
chainer2.add(self.sequelize.query(sql, null, { raw: true, logging: options.logging })) promises.push(self.sequelize.query(sql, null, { raw: true, logging: options.logging }))
} else if (!!results[enumIdx] && !!daoTable) { } else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value) var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values , vals = daoTable.rawAttributes[keys[i]].values
...@@ -133,7 +133,7 @@ module.exports = (function() { ...@@ -133,7 +133,7 @@ module.exports = (function() {
options.after = vals[idx-1] options.after = vals[idx-1]
} }
chainer2.add(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options))) promises.push(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options)))
} }
}) })
enumIdx++ enumIdx++
...@@ -144,21 +144,16 @@ module.exports = (function() { ...@@ -144,21 +144,16 @@ module.exports = (function() {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes) attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options) sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
chainer2.run().success(function() { return Promise.all(promises).then(function() {
queryAndEmit return queryAndEmit.call(self, sql, 'createTable', options)
.call(self, sql, 'createTable', options)
.proxy(emitter, { events: ['success', 'error', 'sql']})
}) })
.proxy(emitter, { events: ['error', 'sql']})
}) })
} else { } else {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes) attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options) sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
queryAndEmit.call(self, sql, 'createTable', options) return queryAndEmit.call(self, sql, 'createTable', options)
.proxy(emitter, { events: ['success', 'error', 'sql']})
} }
}).run()
} }
QueryInterface.prototype.dropTable = function(tableName, options) { QueryInterface.prototype.dropTable = function(tableName, options) {
...@@ -169,10 +164,8 @@ module.exports = (function() { ...@@ -169,10 +164,8 @@ module.exports = (function() {
var sql = this.QueryGenerator.dropTableQuery(tableName, options) var sql = this.QueryGenerator.dropTableQuery(tableName, options)
, self = this , self = this
return new Utils.CustomEventEmitter(function(emitter) { return queryAndEmit.call(this, sql, 'dropTable', options).then(function () {
var chainer = new Utils.QueryChainer() var promises = []
chainer.add(self, 'queryAndEmit', [sql, 'dropTable'], options)
// Since postgres has a special case for enums, we should drop the related // Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute // enum type within the table and attribute
...@@ -193,19 +186,16 @@ module.exports = (function() { ...@@ -193,19 +186,16 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) { for (i = 0; i < keyLen; i++) {
if (daoTable.rawAttributes[keys[i]].type && daoTable.rawAttributes[keys[i]].type.toString() === "ENUM") { if (daoTable.rawAttributes[keys[i]].type && daoTable.rawAttributes[keys[i]].type.toString() === "ENUM") {
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {logging: options.logging, raw: true}]) promises.push(self.sequelize.query(self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {logging: options.logging, raw: true}))
} }
} }
} }
} }
chainer.runSerially() return Promise.all(promises).then(function (results) {
.success(function(results) { return results[0];
emitter.emit('success', results[0]) });
self.emit('dropTable', null)
}) })
.proxy(emitter, { events: ['error', 'sql']})
}).run()
} }
QueryInterface.prototype.dropAllTables = function(options) { QueryInterface.prototype.dropAllTables = function(options) {
...@@ -472,10 +462,12 @@ module.exports = (function() { ...@@ -472,10 +462,12 @@ module.exports = (function() {
} }
QueryInterface.prototype.insert = function(dao, tableName, values, options) { QueryInterface.prototype.insert = function(dao, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes) var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes);
return queryAndEmit.call(this, [sql, dao, options], 'insert', {
success: function(obj) { obj.isNewRecord = false } return queryAndEmit.call(this, [sql, dao, options], 'insert').then(function (result) {
}) result.isNewRecord = false;
return result;
});
} }
QueryInterface.prototype.bulkInsert = function(tableName, records, options, Model) { QueryInterface.prototype.bulkInsert = function(tableName, records, options, Model) {
...@@ -532,16 +524,20 @@ module.exports = (function() { ...@@ -532,16 +524,20 @@ module.exports = (function() {
} }
} }
return new Utils.CustomEventEmitter(function(emitter) { return new Promise(function (resolve, reject) {
var tick = 0 var tick = 0
var iterate = function(err, i) { var iterate = function(err, i) {
if (!!err || i >= cascades.length) { if (err) {
return run(err) return reject(err)
}
if (i >= cascades.length) {
return resolve();
} }
dao[cascades[i]]().success(function(tasks) { dao[cascades[i]]().success(function(tasks) {
if (tasks === null || tasks.length < 1) { if (tasks === null || tasks.length < 1) {
return run() return resolve()
} }
tasks = Array.isArray(tasks) ? tasks : [tasks] tasks = Array.isArray(tasks) ? tasks : [tasks]
...@@ -571,34 +567,14 @@ module.exports = (function() { ...@@ -571,34 +567,14 @@ module.exports = (function() {
}) })
} }
var run = function(err) {
if (!!err) {
return emitter.emit('error', err)
}
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [[sql, dao, options], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}
if (cascades.length > 0) { if (cascades.length > 0) {
iterate(null, tick) iterate(null, tick)
} else { } else {
run() resolve();
} }
}).run() }).then(function () {
return self.queryAndEmit([sql, dao, options], 'delete');
});
} }
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) { QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
...@@ -865,7 +841,7 @@ module.exports = (function() { ...@@ -865,7 +841,7 @@ module.exports = (function() {
}, options || {}) }, options || {})
var execQuery = function(emitter) { var execQuery = function(emitter) {
var query;
if (Array.isArray(sqlOrQueryParams)) { if (Array.isArray(sqlOrQueryParams)) {
if (sqlOrQueryParams.length === 1) { if (sqlOrQueryParams.length === 1) {
sqlOrQueryParams.push(null) sqlOrQueryParams.push(null)
...@@ -875,11 +851,22 @@ module.exports = (function() { ...@@ -875,11 +851,22 @@ module.exports = (function() {
sqlOrQueryParams.push(typeof options === "object" ? options : {}) sqlOrQueryParams.push(typeof options === "object" ? options : {})
} }
emitter.query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams) query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
} else { } else {
emitter.query = this.sequelize.query(sqlOrQueryParams, null, options) query = this.sequelize.query(sqlOrQueryParams, null, options)
} }
if (!emitter) {
return query.then(function (result) {
if (options.success) options.success(result);
return result;
}/*, function (err) {
if (options.error) options.error(err);
}*/);
}
emitter.query = query;
emitter emitter
.query .query
.success(function(obj) { .success(function(obj) {
...@@ -897,12 +884,14 @@ module.exports = (function() { ...@@ -897,12 +884,14 @@ module.exports = (function() {
emitter.emit('error', err) emitter.emit('error', err)
}.bind(this)) }.bind(this))
.proxy(emitter, { events: ['sql'] }) .proxy(emitter, { events: ['sql'] })
return emitter;
}.bind(this) }.bind(this)
if (!!emitter) { if (!!emitter) {
execQuery(emitter) execQuery(emitter)
} else { } else {
return new Utils.CustomEventEmitter(execQuery).run() return execQuery();
} }
} }
......
...@@ -10,6 +10,7 @@ var url = require("url") ...@@ -10,6 +10,7 @@ var url = require("url")
, TransactionManager = require('./transaction-manager') , TransactionManager = require('./transaction-manager')
, QueryTypes = require('./query-types') , QueryTypes = require('./query-types')
, sequelizeErrors = require('./errors') , sequelizeErrors = require('./errors')
, Promise = require('./promise')
/** /**
* This is the main class, the entry point to sequelize. To use it, you just need to import sequelize: * This is the main class, the entry point to sequelize. To use it, you just need to import sequelize:
...@@ -508,7 +509,7 @@ module.exports = (function() { ...@@ -508,7 +509,7 @@ module.exports = (function() {
type: (sql.toLowerCase().indexOf('select') === 0) ? QueryTypes.SELECT : false type: (sql.toLowerCase().indexOf('select') === 0) ? QueryTypes.SELECT : false
}) })
return this.transactionManager.query(sql, callee, options) return this.transactionManager.query(sql, callee, options);
} }
/** /**
...@@ -823,5 +824,7 @@ module.exports = (function() { ...@@ -823,5 +824,7 @@ module.exports = (function() {
} }
} }
Sequelize.Promise = Promise
return Sequelize return Sequelize
})() })()
...@@ -606,5 +606,6 @@ Utils.col.prototype.toString = function (queryGenerator, parentModel) { ...@@ -606,5 +606,6 @@ Utils.col.prototype.toString = function (queryGenerator, parentModel) {
} }
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter") Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.Promise = require(__dirname + "/promise")
Utils.QueryChainer = require(__dirname + "/query-chainer") Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
"pg": "~2.8.1", "pg": "~2.8.1",
"watchr": "~2.4.11", "watchr": "~2.4.11",
"chai": "~1.9.0", "chai": "~1.9.0",
"mocha": "~1.17.0", "mocha": "~1.18.2",
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.8.1", "sinon": "~1.8.1",
"mariasql": "0.1.20", "mariasql": "0.1.20",
......
...@@ -4,6 +4,7 @@ var chai = require('chai') ...@@ -4,6 +4,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types") , DataTypes = require(__dirname + "/../../lib/data-types")
, Sequelize = require('../../index') , Sequelize = require('../../index')
, assert = require('assert')
chai.config.includeStack = true chai.config.includeStack = true
...@@ -359,14 +360,17 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() { ...@@ -359,14 +360,17 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
User.create({ username: 'foo' }).success(function(user) { User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) { Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() { task.setUser(user).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction // Should fail due to FK restriction
user.destroy().then(function () {
assert(false);
}, function(err) {
expect(err).to.be.ok;
Task.findAll().success(function(tasks) { Task.findAll().success(function(tasks) {
expect(tasks).to.have.length(1) expect(tasks).to.have.length(1)
done() done()
}) })
}) })
}) });
}) })
}) })
}) })
......
...@@ -1323,7 +1323,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -1323,7 +1323,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
describe('removing from the join table', function () { describe('removing from the join table', function () {
it('should remove a single entry without any attributes (and timestamps off) on the through model', function (done) { it('should remove a single entry without any attributes (and timestamps off) on the through model', function () {
var Worker = this.sequelize.define('Worker', {}, {timestamps: false}) var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
, Task = this.sequelize.define('Task', {}, {timestamps: false}) , Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false}) , WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false})
...@@ -1331,28 +1331,24 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -1331,28 +1331,24 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Worker.hasMany(Task, { through: WorkerTasks }) Worker.hasMany(Task, { through: WorkerTasks })
Task.hasMany(Worker, { through: WorkerTasks }) Task.hasMany(Worker, { through: WorkerTasks })
this.sequelize.sync().done(function(err) { // Test setup
expect(err).not.to.be.ok return this.sequelize.sync().then(function() {
Worker.create({}).done(function (err, worker) { return Sequelize.Promise.all([
expect(err).not.to.be.ok Worker.create({}),
Task.bulkCreate([{}, {}]).done(function (err) { Task.bulkCreate([{}, {}]).then(function () {
expect(err).not.to.be.ok return Task.findAll()
Task.findAll().done(function (err, tasks) { })
expect(err).not.to.be.ok ]);
worker.setTasks(tasks).done(function () { }).spread(function (worker, tasks) {
worker.removeTask(tasks[0]).done(function (err) { // Set all tasks, then remove one tasks, then return all tasks
expect(err).not.to.be.ok return worker.setTasks(tasks).then(function () {
return worker.removeTask(tasks[0]);
worker.getTasks().done(function (err, tasks) { }).then(function () {
expect(tasks.length).to.equal(1) return worker.getTasks();
done() });
}) }).then(function (tasks) {
}) expect(tasks.length).to.equal(1);
}) });
})
})
})
})
}) })
}) })
}) })
...@@ -1385,7 +1381,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -1385,7 +1381,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
.then(function() { return b1.save() }) .then(function() { return b1.save() })
.then(function() { return a1.setRelation1(b1) }) .then(function() { return a1.setRelation1(b1) })
.then(function() { return self.A.find({ where: { name: 'a1' } }) }) .then(function() { return self.A.find({ where: { name: 'a1' } }) })
.done(function(a) { .then(function(a) {
expect(a.relation1Id).to.be.eq(b1.id) expect(a.relation1Id).to.be.eq(b1.id)
done() done()
}) })
...@@ -1414,7 +1410,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -1414,7 +1410,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
.then(function() { return b1.save() }) .then(function() { return b1.save() })
.then(function() { return b1.setRelation1(a1) }) .then(function() { return b1.setRelation1(a1) })
.then(function() { return self.B.find({ where: { name: 'b1' } }) }) .then(function() { return self.B.find({ where: { name: 'b1' } }) })
.done(function(b) { .then(function(b) {
expect(b.relation1Id).to.be.eq(a1.id) expect(b.relation1Id).to.be.eq(a1.id)
done() done()
}) })
......
...@@ -1572,26 +1572,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1572,26 +1572,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
describe('references', function() { describe('references', function() {
beforeEach(function(done) { beforeEach(function() {
var self = this var self = this
this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() { this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING })
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
self.Author = self.sequelize.define('author', { firstName: Sequelize.STRING }) return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(function() {
self.Author.sync().success(function() { return self.sequelize.getQueryInterface().dropTable('authors', { force: true })
done() }).then(function() {
}) return self.Author.sync()
})
}) })
}) })
afterEach(function(done) { afterEach(function() {
var self = this var self = this
this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() { return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() { return self.sequelize.getQueryInterface().dropTable('authors', { force: true })
done()
})
}) })
}) })
...@@ -1680,7 +1677,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1680,7 +1677,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(2).to.equal(1) expect(2).to.equal(1)
done() done()
} }
}).error(function(err) {
return;
}).catch(function(err) {
if (Support.dialectIsMySQL(true)) { if (Support.dialectIsMySQL(true)) {
expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/) expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/)
} else if (dialect === 'mariadb') { } else if (dialect === 'mariadb') {
......
...@@ -8,6 +8,7 @@ var chai = require('chai') ...@@ -8,6 +8,7 @@ var chai = require('chai')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, datetime = require('chai-datetime') , datetime = require('chai-datetime')
, _ = require('lodash') , _ = require('lodash')
, assert = require('assert')
chai.use(datetime) chai.use(datetime)
chai.config.includeStack = true chai.config.includeStack = true
...@@ -165,7 +166,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -165,7 +166,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(err).not.to.be.ok expect(err).not.to.be.ok
User.create({email: 'hello@sequelize.com'}).done(function (err) { User.create({email: 'hello@sequelize.com'}).done(function (err) {
expect(err).not.to.be.ok expect(err).not.to.be.ok
User.create({email: 'hello@sequelize.com'}).done(function (err) { User.create({email: 'hello@sequelize.com'}).then(function () {
assert(false)
}, function (err) {
expect(err).to.be.ok expect(err).to.be.ok
expect(err).to.be.an.instanceof(Error) expect(err).to.be.an.instanceof(Error)
done() done()
......
...@@ -507,6 +507,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -507,6 +507,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
it('produce 3 errors', function(done) { it('produce 3 errors', function(done) {
this.Project.create({}).error(function(err) { this.Project.create({}).error(function(err) {
expect(err).to.be.an.instanceOf(Error) expect(err).to.be.an.instanceOf(Error)
delete err.stack // longStackTraces
expect(Object.keys(err)).to.have.length(3) expect(Object.keys(err)).to.have.length(3)
done() done()
}) })
......
...@@ -2,8 +2,11 @@ var chai = require('chai') ...@@ -2,8 +2,11 @@ var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, SequelizePromise = require(__dirname + "/../lib/promise")
, Promise = require('bluebird')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, _ = require('lodash') , _ = require('lodash')
, sinon = require('sinon')
chai.config.includeStack = true chai.config.includeStack = true
...@@ -347,15 +350,231 @@ describe(Support.getTestDialectTeaser("Promise"), function () { ...@@ -347,15 +350,231 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
done() done()
}) })
}) })
it('works for functions with only one return value', function (done) { })
this.User })
.find({ id: 1})
.spread(function(user) { describe('backwards compat', function () {
expect(user.id).to.equal(1) it('should still work with .complete() when resolving', function(done) {
expect(arguments.length).to.equal(1) var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('abc');
});
promise.complete(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal([null, 'abc']);
done()
});
});
it('should still work with .success() when resolving', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yay');
});
promise.success(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yay']);
done()
});
});
it('should still work with .on(\'success\') when resolving', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yoohoo');
});
promise.on('success', spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yoohoo']);
done()
});
});
it('should still work with .done() when resolving multiple results', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve(Promise.all(['MyModel', true]));
});
promise.spread(spy);
promise.done(function (err, model, created) {
expect(model).to.equal('MyModel')
expect(created).to.be.true
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['MyModel', true]);
done()
});
});
it('should still work with .complete() after chaining', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('Heyo');
});
promise.then(function (result) {
return result+'123';
}).complete(function (err, result) {
expect(err).not.to.be.ok;
expect(result).to.equal('Heyo123');
done();
});
});
it('should still work with .success() when emitting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
// no-op
});
promise.success(spy);
promise.then(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args).to.deep.equal(['yay']);
done()
});
promise.emit('success', 'yay');
});
it('should still work with .done() when rejecting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
reject(new Error('no'));
});
promise.done(spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .error() when throwing', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
throw new Error('no');
});
promise.error(spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .on(\'error\') when throwing', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
throw new Error('noway');
});
promise.on('error', spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
});
it('should still work with .error() when emitting', function(done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
// no-op
});
promise.on('error', spy);
promise.catch(function () {
expect(spy.calledOnce).to.be.true
expect(spy.firstCall.args[0]).to.be.an.instanceof(Error)
done()
});
promise.emit('error', new Error('noway'));
});
it('should still support sql events', function (done) {
var spy = sinon.spy()
, promise = new SequelizePromise(function (resolve, reject) {
resolve('yay');
});
promise.on('sql', spy);
promise.emit('sql', 'SQL STATEMENT 1');
promise.emit('sql', 'SQL STATEMENT 2');
promise.then(function () {
expect(spy.calledTwice).to.be.true;
done();
});
});
describe("proxy", function () {
it("should correctly work with success listeners", function(done) {
var emitter = new SequelizePromise(function () {})
, proxy = new SequelizePromise(function () {})
, success = sinon.spy()
emitter.success(success)
proxy.success(function () {
process.nextTick(function () {
expect(success.called).to.be.true
done() done()
}) })
}) })
proxy.proxy(emitter)
proxy.emit('success')
})
it("should correctly work with complete/done listeners", function(done) {
var promise = new SequelizePromise(function () {})
, proxy = new SequelizePromise(function () {})
, complete = sinon.spy()
promise.complete(complete)
proxy.complete(function() {
process.nextTick(function() {
expect(complete.called).to.be.true
done()
}) })
}) })
})
proxy.proxy(promise)
proxy.emit('success')
})
})
describe("when emitting an error event with an array of errors", function() {
describe("if an error handler is given", function() {
it("should return the whole array", function(done) {
var emitter = new SequelizePromise(function () {})
var errors = [
[
new Error("First error"),
new Error("Second error")
], [
new Error("Third error")
]
]
emitter.error(function (err) {
expect(err).to.equal(errors)
done()
})
emitter.emit("error", errors)
})
})
})
});
});
\ No newline at end of file
...@@ -5,6 +5,12 @@ var fs = require('fs') ...@@ -5,6 +5,12 @@ var fs = require('fs')
, DataTypes = require(__dirname + "/../lib/data-types") , DataTypes = require(__dirname + "/../lib/data-types")
, Config = require(__dirname + "/config/config") , Config = require(__dirname + "/config/config")
// Make sure errors get thrown when testing
Sequelize.Promise.onPossiblyUnhandledRejection(function(e, promise) {
throw e;
});
Sequelize.Promise.longStackTraces();
var Support = { var Support = {
Sequelize: Sequelize, Sequelize: Sequelize,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!