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

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) if (oldInstance) {
.success(function(oldInstance) { oldInstance[association.identifier] = null
if (oldInstance) { return oldInstance.save(Utils._.extend({}, options, {
oldInstance[association.identifier] = null fields: [association.identifier],
oldInstance allowNull: [association.identifier],
.save(Utils._.extend({}, options, { association: true
fields: [association.identifier], }))
allowNull: [association.identifier], }
association: true }).then(function () {
})) if (associatedInstance) {
.success(function() { associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
if (associatedInstance) { return associatedInstance.save(options)
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier)) }
associatedInstance.save(options).proxy(emitter) return null;
} else { })
emitter.emit('success', null)
}
})
} else {
if (associatedInstance) {
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
associatedInstance.save(options).proxy(emitter)
} else {
emitter.emit('success', 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 { return doQuery().return(this)
doQuery() }
}
}).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) { }).then(function () {
if (!!error) { return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance);
return emitter.emit('error', error) }).return(self.modelInstance);
}
self.modelInstance.Model.runHooks('afterValidate', self.modelInstance, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', self.modelInstance)
})
})
})
}).run()
} }
/** /**
......
...@@ -436,134 +436,119 @@ module.exports = (function() { ...@@ -436,134 +436,119 @@ 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) }).then(function () {
}).proxy(emitter, { events: ['error'] }).success(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] }
} })
})
for (var attrName in self.Model.rawAttributes) { for (var attrName in self.Model.rawAttributes) {
if (self.Model.rawAttributes.hasOwnProperty(attrName)) { if (self.Model.rawAttributes.hasOwnProperty(attrName)) {
var definition = self.Model.rawAttributes[attrName] var definition = self.Model.rawAttributes[attrName]
, isEnum = !!definition.type && (definition.type.toString() === DataTypes.ENUM.toString()) , isEnum = !!definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = ['mysql', 'mariadb'].indexOf(self.Model.daoFactoryManager.sequelize.options.dialect) !== -1 , isMySQL = ['mysql', 'mariadb'].indexOf(self.Model.daoFactoryManager.sequelize.options.dialect) !== -1
, ciCollation = !!self.Model.options.collate && self.Model.options.collate.match(/_ci$/i) , ciCollation = !!self.Model.options.collate && self.Model.options.collate.match(/_ci$/i)
, valueOutOfScope , valueOutOfScope
// Unfortunately for MySQL CI collation we need to map/lowercase values again // Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) { if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase()) var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1 valueOutOfScope = scopeIndex === -1
// We'll return what the actual case will be, since a simple SELECT query would do the same... // We'll return what the actual case will be, since a simple SELECT query would do the same...
if (!valueOutOfScope) { if (!valueOutOfScope) {
values[attrName] = definition.values[scopeIndex] values[attrName] = definition.values[scopeIndex]
}
} }
} }
} }
}
if (updatedAtAttr) { if (updatedAtAttr) {
values[updatedAtAttr] = ( values[updatedAtAttr] = (
( (
self.isNewRecord self.isNewRecord
&& !!self.Model.rawAttributes[updatedAtAttr] && !!self.Model.rawAttributes[updatedAtAttr]
&& !!self.Model.rawAttributes[updatedAtAttr].defaultValue && !!self.Model.rawAttributes[updatedAtAttr].defaultValue
) )
? self.Model.rawAttributes[updatedAtAttr].defaultValue ? self.Model.rawAttributes[updatedAtAttr].defaultValue
: Utils.now(self.sequelize.options.dialect)) : Utils.now(self.sequelize.options.dialect))
}
if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
values[createdAtAttr] = (
(
!!self.Model.rawAttributes[createdAtAttr]
&& !!self.Model.rawAttributes[createdAtAttr].defaultValue
)
? self.Model.rawAttributes[createdAtAttr].defaultValue
: Utils.now(self.sequelize.options.dialect))
} }
if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
values[createdAtAttr] = (
(
!!self.Model.rawAttributes[createdAtAttr]
&& !!self.Model.rawAttributes[createdAtAttr].defaultValue
)
? self.Model.rawAttributes[createdAtAttr].defaultValue
: Utils.now(self.sequelize.options.dialect))
}
var query = null
, args = []
, hook = ''
if (self.isNewRecord) { var query = null
query = 'insert' , args = []
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, options] , hook = ''
hook = 'Create'
} else {
var identifier = self.primaryKeyValues
if (identifier === null && self.__options.whereCollection !== null) { if (self.isNewRecord) {
identifier = self.__options.whereCollection; query = 'insert'
} args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, options]
hook = 'Create'
} else {
var identifier = self.primaryKeyValues
query = 'update' if (identifier === null && self.__options.whereCollection !== null) {
args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, identifier, options] identifier = self.__options.whereCollection;
hook = 'Update'
} }
// Add the values to the DAO query = 'update'
self.dataValues = _.extend(self.dataValues, values) args = [self, self.QueryInterface.QueryGenerator.addSchema(self.Model), values, identifier, options]
hook = 'Update'
}
// Run the beforeCreate / beforeUpdate hook // Add the values to the DAO
self.Model.runHooks('before' + hook, self, function(err) { self.dataValues = _.extend(self.dataValues, values)
if (!!err) {
return emitter.emit('error', err) return self.Model.runHooks('before' + hook, self).then(function () {
// dataValues might have changed inside the hook, rebuild
// the values hash
values = {}
options.fields.forEach(function(field) {
if (self.dataValues[field] !== undefined) {
values[field] = self.dataValues[field]
} }
})
args[2] = values
// dataValues might have changed inside the hook, rebuild return self.QueryInterface[query].apply(self.QueryInterface, args).catch(function(err) {
// the values hash if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) {
values = {} var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString())
options.fields.forEach(function(field) { if (fields !== false) {
if (self.dataValues[field] !== undefined) { fields = fields.filter(function(f) { return f !== self.Model.tableName; })
values[field] = self.dataValues[field] Utils._.each(self.__options.uniqueKeys, function(value) {
} if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
}) err = new Error(value.msg)
args[2] = values
self.QueryInterface[query].apply(self.QueryInterface, args)
.proxy(emitter, {events: ['sql']})
.error(function(err) {
if (!!self.__options.uniqueKeys && err.code && self.QueryInterface.QueryGenerator.uniqueConstraintMapping.code === err.code) {
var fields = self.QueryInterface.QueryGenerator.uniqueConstraintMapping.map(err.toString())
if (fields !== false) {
fields = fields.filter(function(f) { return f !== self.Model.tableName; })
Utils._.each(self.__options.uniqueKeys, function(value, key) {
if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
err = new Error(value.msg)
}
})
} }
} })
}
}
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)
// Ensure new values are on DAO, and reset previousDataValues // Ensure new values are on DAO, and reset previousDataValues
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() })
} }
/* /*
...@@ -653,40 +638,24 @@ module.exports = (function() { ...@@ -653,40 +638,24 @@ module.exports = (function() {
options = options || {} options = options || {}
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) {
self.Model.runHooks(self.Model.options.hooks.beforeDestroy, self, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
if (self.Model._timestampAttributes.deletedAt && options.force === false) {
self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date()
query = self.save(options)
} else {
var identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.Model), identifier, options)
}
query.on('sql', function(sql) { // 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
emitter.emit('sql', sql) return self.Model.runHooks(self.Model.options.hooks.beforeDestroy, self).then(function () {
}) var query
.error(function(err) { , identifier
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) if (self.Model._timestampAttributes.deletedAt && options.force === false) {
}) self.dataValues[self.Model._timestampAttributes.deletedAt] = new Date()
}) query = self.save(options)
}) } else {
}).run() identifier = self.__options.hasPrimaryKeys ? self.primaryKeyValues : { id: self.id };
query = self.QueryInterface.delete(self, self.QueryInterface.QueryGenerator.addSchema(self.Model), identifier, options)
}
return query;
}).then(function (results) {
return self.Model.runHooks(self.Model.options.hooks.afterDestroy, self).return(results);
});
} }
/** /**
......
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() var query = new Query(self.client, self.sequelize, callee, options || {})
.on('error', function(err) {
emitter.emit('error', err) // We return the query regardless of error or success in the query
}) return query.run(sql).finally(function () {
.on('success', function(done) { self.endQuery.call(self);
var query = new Query(self.client, self.sequelize, callee, options || {}) done && done();
});
return query.run(sql) });
.complete(function(err) {
self.endQuery.call(self)
done && done(err) })
.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,138 +32,138 @@ module.exports = (function() { ...@@ -31,138 +32,138 @@ module.exports = (function() {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql) this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql)
} }
query.on('row', function(row) { return new Promise(function (resolve, reject) {
rows.push(row) var promise = this;
})
query.on('error', function(err) {
receivedError = true
err.sql = sql
self.emit('sql', sql, self.options.uuid)
self.emit('error', err, self.callee)
})
query.on('end', function(result) {
self.emit('sql', self.sql, self.options.uuid)
if (receivedError) {
return
}
onSuccess.call(self, rows, sql, result) query.on('row', function(row) {
}) rows.push(row)
})
return this query.on('error', function(err) {
} receivedError = true
err.sql = sql
promise.emit('sql', sql, self.options.uuid)
reject(err);
})
Query.prototype.getInsertIdField = function() { query.on('end', function(result) {
return 'id' if (receivedError) {
} return;
}
var onSuccess = function(rows, sql, result) { promise.emit('sql', self.sql, self.options.uuid)
var results = rows resolve([rows, sql, result]);
, self = this })
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) }).spread(function (rows, sql, result) {
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
if (isTableNameQuery || isRelNameQuery) { , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
if (isRelNameQuery) {
results = rows.map(function(row) { if (isTableNameQuery || isRelNameQuery) {
return { if (isRelNameQuery) {
name: row.relname, results = rows.map(function(row) {
tableName: row.relname.split('_')[0] return {
} name: row.relname,
}) tableName: row.relname.split('_')[0]
} else { }
results = rows.map(function(row) { return Utils._.values(row) }) })
} else {
results = rows.map(function(row) { return Utils._.values(row) })
}
return results
} }
return this.emit('success', 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] = {
type: _result.Type.toUpperCase(), type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'), allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default, defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : []) special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
} }
if (result[_result.Field].type === 'BOOLEAN') { if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue] result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue]
if (result[_result.Field].defaultValue === undefined) { if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null result[_result.Field].defaultValue = null
}
} }
}
if (typeof result[_result.Field].defaultValue === 'string') { if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, "") result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, "")
if (result[_result.Field].defaultValue.indexOf('::') > -1) { if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::') var split = result[_result.Field].defaultValue.split('::')
if (split[1].toLowerCase() !== "regclass)") { if (split[1].toLowerCase() !== "regclass)") {
result[_result.Field].defaultValue = split[0] result[_result.Field].defaultValue = split[0]
}
} }
} }
} })
})
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]
if (targetAttr != key) { if (targetAttr != key) {
row[targetAttr] = row[key] row[targetAttr] = row[key]
delete row[key] delete row[key]
} }
})
}) })
}) }
}
// 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)) {
row[key] = hstore.parse(row[key]) row[key] = hstore.parse(row[key])
} }
})
}) })
}) }
}
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)
}
self.callee.dataValues[key] = record
} }
this.callee.dataValues[key] = record
} }
} }
return self.callee
} else {
return results
} }
})
this.emit('success', this.callee) return this
} else { }
this.emit('success', results)
} 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:
...@@ -55,11 +56,12 @@ Hooks.replaceHookAliases = function(hooks) { ...@@ -55,11 +56,12 @@ Hooks.replaceHookAliases = function(hooks) {
} }
Hooks.runHooks = function() { 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,32 +71,38 @@ Hooks.runHooks = function() { ...@@ -69,32 +71,38 @@ Hooks.runHooks = function() {
hooks = hooks === undefined ? [] : [hooks] hooks = hooks === undefined ? [] : [hooks]
} }
if (hooks.length < 1) { return new Promise(function (resolve, reject) {
return fn.apply(this, [null].concat(args)) if (hooks.length < 1) {
} return resolve(args)
var run = function(hook) {
if (!hook) {
return fn.apply(this, [null].concat(args))
} }
if (typeof hook === "object") { var run = function(hook) {
hook = hook.fn if (!hook) {
} return resolve(args)
}
hook.apply(self, args.concat(function() {
tick++
if (!!arguments[0]) { if (typeof hook === "object") {
return fn(arguments[0]) hook = hook.fn
} }
// daoValues = newValues hook.apply(self, args.concat(function() {
return run(hooks[tick]) tick++
}))
} if (!!arguments[0]) {
return reject(arguments[0])
}
run(hooks[tick]) // daoValues = newValues
return 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,78 +88,72 @@ module.exports = (function() { ...@@ -87,78 +88,72 @@ 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 promises = []
var chainer = new Utils.QueryChainer() // 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
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) promises.push(self.sequelize.query(sql, null, { plain: true, raw: true, type: QueryTypes.SELECT, logging: options.logging }))
chainer.add(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
daoTable = daoTable.length > 0 ? daoTable[0] : null daoTable = daoTable.length > 0 ? daoTable[0] : null
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")) {
// 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
vals.forEach(function(value, idx) { vals.forEach(function(value, idx) {
// reset out after/before options since it's for every enum value // reset out after/before options since it's for every enum value
options.before = null options.before = null
options.after = null options.after = null
if (enumVals.indexOf(value) === -1) { if (enumVals.indexOf(value) === -1) {
if (!!vals[idx+1]) { if (!!vals[idx+1]) {
options.before = vals[idx+1] options.before = vals[idx+1]
}
else if (!!vals[idx-1]) {
options.after = vals[idx-1]
}
chainer2.add(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options)))
} }
}) else if (!!vals[idx-1]) {
enumIdx++ options.after = vals[idx-1]
} }
promises.push(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options)))
}
})
enumIdx++
} }
} }
}
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
chainer2.run().success(function() {
queryAndEmit
.call(self, sql, 'createTable', options)
.proxy(emitter, { events: ['success', 'error', 'sql']})
})
.proxy(emitter, { events: ['error', 'sql']})
})
} 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 Promise.all(promises).then(function() {
.proxy(emitter, { events: ['success', 'error', 'sql']}) return queryAndEmit.call(self, sql, 'createTable', options)
} })
}).run() })
} else {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
return queryAndEmit.call(self, sql, 'createTable', options)
}
} }
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) {
...@@ -530,18 +522,22 @@ module.exports = (function() { ...@@ -530,18 +522,22 @@ 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 () {
worker.removeTask(tasks[0]).done(function (err) {
expect(err).not.to.be.ok
worker.getTasks().done(function (err, tasks) {
expect(tasks.length).to.equal(1)
done()
})
})
})
})
}) })
}) ]);
}) }).spread(function (worker, tasks) {
// Set all tasks, then remove one tasks, then return all tasks
return worker.setTasks(tasks).then(function () {
return worker.removeTask(tasks[0]);
}).then(function () {
return worker.getTasks();
});
}).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()
}) })
......
...@@ -601,7 +601,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -601,7 +601,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()
}) })
}) })
......
...@@ -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()
})
})
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() 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!