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

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() {
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
var instance = this
return new Utils.CustomEventEmitter(function(emitter) {
instance[association.accessors.get](options)
.success(function(oldInstance) {
return instance[association.accessors.get](options).then(function(oldInstance) {
if (oldInstance) {
oldInstance[association.identifier] = null
oldInstance
.save(Utils._.extend({}, options, {
return oldInstance.save(Utils._.extend({}, options, {
fields: [association.identifier],
allowNull: [association.identifier],
association: true
}))
.success(function() {
if (associatedInstance) {
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
associatedInstance.save(options).proxy(emitter)
} else {
emitter.emit('success', null)
}
})
} else {
}).then(function () {
if (associatedInstance) {
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier))
associatedInstance.save(options).proxy(emitter)
} else {
emitter.emit('success', null)
}
return associatedInstance.save(options)
}
return null;
})
.proxy(emitter, { events: ['sql'] })
}).run()
}
return this
......
......@@ -336,25 +336,17 @@ module.exports = (function() {
options = Utils._.extend({}, this.options, options || {})
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var doQuery = function() {
self
.QueryInterface
.createTable(self.getTableName(), self.attributes, options)
.proxy(emitter, {events: ['error', 'sql']})
.success(function() { emitter.emit('success', self) })
, doQuery = function() {
return self.QueryInterface.createTable(self.getTableName(), self.attributes, options);
}
if (options.force) {
self
.drop(options)
.proxy(emitter, {events: ['error', 'sql']})
.success(doQuery)
return self.drop(options).then(function () {
return doQuery().return(self);
});
} else {
doQuery()
return doQuery().return(this)
}
}).run()
}
/**
......@@ -1224,7 +1216,6 @@ module.exports = (function() {
if (!!err) {
return emitter.emit('error', err)
}
daos = newRecords || daos
options.fields = newFields || options.fields
......
......@@ -166,27 +166,15 @@ DaoValidator.prototype.validate = function() {
DaoValidator.prototype.hookValidate = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
self.validate().success(function (error) {
if (!!error) {
return emitter.emit('error', error)
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance).then(function () {
return self.validate().then(function (error) {
if (error) {
throw error
}
self.modelInstance.Model.runHooks('afterValidate', self.modelInstance, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', self.modelInstance)
})
})
})
}).run()
});
}).then(function () {
return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance);
}).return(self.modelInstance);
}
/**
......
......@@ -436,10 +436,9 @@ module.exports = (function() {
}
}
return new Utils.CustomEventEmitter(function(emitter) {
self.hookValidate({
return self.hookValidate({
skip: _.difference(Object.keys(self.rawAttributes), options.fields)
}).proxy(emitter, { events: ['error'] }).success(function() {
}).then(function () {
options.fields.forEach(function(field) {
if (self.dataValues[field] !== undefined) {
values[field] = self.dataValues[field]
......@@ -511,12 +510,7 @@ module.exports = (function() {
// Add the values to the DAO
self.dataValues = _.extend(self.dataValues, values)
// Run the beforeCreate / beforeUpdate hook
self.Model.runHooks('before' + hook, self, function(err) {
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 = {}
......@@ -528,15 +522,13 @@ module.exports = (function() {
})
args[2] = values
self.QueryInterface[query].apply(self.QueryInterface, args)
.proxy(emitter, {events: ['sql']})
.error(function(err) {
return self.QueryInterface[query].apply(self.QueryInterface, args).catch(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) {
Utils._.each(self.__options.uniqueKeys, function(value) {
if (Utils._.isEqual(value.fields, fields) && !!value.msg) {
err = new Error(value.msg)
}
......@@ -544,9 +536,8 @@ module.exports = (function() {
}
}
emitter.emit('error', err)
})
.success(function(result) {
throw err
}).then(function(result) {
// Transfer database generated values (defaults, autoincrement, etc)
values = _.extend(values, result.dataValues)
......@@ -554,16 +545,10 @@ module.exports = (function() {
result.dataValues = _.extend(result.dataValues, values)
result._previousDataValues = _.clone(result.dataValues)
self.Model.runHooks('after' + hook, result, function(err) {
if (!!err) {
return emitter.emit('error', err)
}
emitter.emit('success', result)
})
return self.Model.runHooks('after' + hook, result).return(result)
})
})
})
}).run()
}
/*
......@@ -654,39 +639,23 @@ module.exports = (function() {
options.force = options.force === undefined ? false : Boolean(options.force)
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)
}
// 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
return self.Model.runHooks(self.Model.options.hooks.beforeDestroy, self).then(function () {
var query
, identifier
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 };
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) {
emitter.emit('sql', sql)
})
.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()
return query;
}).then(function (results) {
return self.Model.runHooks(self.Model.options.hooks.afterDestroy, self).return(results);
});
}
/**
......
var Utils = require('../../utils')
, CustomEventEmitter = require("../../emitters/custom-event-emitter")
, Promise = require("../../promise")
, Dot = require('dottie')
, _ = require('lodash')
, QueryTypes = require('../../query-types')
......
......@@ -47,21 +47,15 @@ module.exports = (function() {
self.pendingQueries++
self.clientDrained = false
return new Utils.CustomEventEmitter(function(emitter) {
self.connect()
.on('error', function(err) {
emitter.emit('error', err)
})
.on('success', function(done) {
return self.connect().then(function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {})
return query.run(sql)
.complete(function(err) {
self.endQuery.call(self)
done && done(err) })
.proxy(emitter)
})
}).run()
// We return the query regardless of error or success in the query
return query.run(sql).finally(function () {
self.endQuery.call(self);
done && done();
});
});
}
ConnectorManager.prototype.afterTransactionSetup = function(callback) {
......@@ -70,7 +64,7 @@ module.exports = (function() {
ConnectorManager.prototype.connect = function(callback) {
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
// TODO: We really need some sort of queue/flush/drain mechanism
......
......@@ -3,6 +3,7 @@ var Utils = require("../../utils")
, DataTypes = require('../../data-types')
, hstore = require('./hstore')
, QueryTypes = require('../../query-types')
, Promise = require('../../promise')
module.exports = (function() {
var Query = function(client, sequelize, callee, options) {
......@@ -31,6 +32,9 @@ module.exports = (function() {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql)
}
return new Promise(function (resolve, reject) {
var promise = this;
query.on('row', function(row) {
rows.push(row)
})
......@@ -38,30 +42,20 @@ module.exports = (function() {
query.on('error', function(err) {
receivedError = true
err.sql = sql
self.emit('sql', sql, self.options.uuid)
self.emit('error', err, self.callee)
promise.emit('sql', sql, self.options.uuid)
reject(err);
})
query.on('end', function(result) {
self.emit('sql', self.sql, self.options.uuid)
if (receivedError) {
return
return;
}
onSuccess.call(self, rows, sql, result)
promise.emit('sql', self.sql, self.options.uuid)
resolve([rows, sql, result]);
})
return this
}
Query.prototype.getInsertIdField = function() {
return 'id'
}
var onSuccess = function(rows, sql, result) {
}).spread(function (rows, sql, result) {
var results = rows
, self = this
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
......@@ -76,12 +70,12 @@ module.exports = (function() {
} else {
results = rows.map(function(row) { return Utils._.values(row) })
}
return this.emit('success', results)
return results
}
if (this.send('isSelectQuery')) {
if (this.sql.toLowerCase().indexOf('select c.column_name') === 0) {
var result = {}
if (self.send('isSelectQuery')) {
if (self.sql.toLowerCase().indexOf('select c.column_name') === 0) {
result = {}
rows.forEach(function(_result) {
result[_result.Field] = {
......@@ -111,12 +105,12 @@ module.exports = (function() {
}
})
this.emit('success', result)
return result
} else {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key]
......@@ -130,7 +124,7 @@ module.exports = (function() {
// Parse hstore fields if the model has any hstore fields.
// 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) {
Utils._.keys(row).forEach(function(key) {
if (self.callee._isHstoreAttribute(key)) {
......@@ -140,29 +134,36 @@ module.exports = (function() {
})
}
this.emit('success', this.send('handleSelectQuery', rows))
return self.send('handleSelectQuery', rows)
}
} else if (this.send('isShowOrDescribeQuery')) {
this.emit('success', results)
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(this.options.type) !== -1) {
this.emit('success', result.rowCount)
} else if (this.send('isInsertQuery') || this.send('isUpdateQuery')) {
if (this.callee !== null) { // may happen for bulk inserts or bulk updates
} else if (self.send('isShowOrDescribeQuery')) {
return results
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) {
return result.rowCount
} else if (self.send('isInsertQuery') || self.send('isUpdateQuery')) {
if (self.callee !== null) { // may happen for bulk inserts or bulk updates
for (var key in rows[0]) {
if (rows[0].hasOwnProperty(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)
}
this.callee.dataValues[key] = record
self.callee.dataValues[key] = record
}
}
}
this.emit('success', this.callee)
return self.callee
} else {
this.emit('success', results)
return results
}
})
return this
}
Query.prototype.getInsertIdField = function() {
return 'id'
}
return Query
......
var util = require("util")
, EventEmitter = require("events").EventEmitter
, Promise = require("bluebird")
, Promise = require("../promise")
, proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('../utils')
......@@ -214,6 +214,12 @@ module.exports = (function() {
return this
}
CustomEventEmitter.prototype.proxySql = function(promise) {
return this.proxy(promise, {
events: ['sql']
})
}
/**
* Attach listeners to the emitter, promise style.
*
......@@ -227,10 +233,13 @@ module.exports = (function() {
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
var promise = (new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', resolve)
}).then(onFulfilled, onRejected)
})).then(onFulfilled, onRejected)
this.proxySql(promise);
return promise;
}
/**
......@@ -246,12 +255,15 @@ module.exports = (function() {
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
var promise = (new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', function () {
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")
, 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:
......@@ -58,8 +59,9 @@ Hooks.runHooks = function() {
var self = this
, tick = 0
, hooks = arguments[0]
, args = Array.prototype.slice.call(arguments, 1, arguments.length-1)
, fn = arguments[arguments.length-1]
, lastIndex = 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") {
hooks = this.options.hooks[hooks] || []
......@@ -69,13 +71,14 @@ Hooks.runHooks = function() {
hooks = hooks === undefined ? [] : [hooks]
}
return new Promise(function (resolve, reject) {
if (hooks.length < 1) {
return fn.apply(this, [null].concat(args))
return resolve(args)
}
var run = function(hook) {
if (!hook) {
return fn.apply(this, [null].concat(args))
return resolve(args)
}
if (typeof hook === "object") {
......@@ -86,7 +89,7 @@ Hooks.runHooks = function() {
tick++
if (!!arguments[0]) {
return fn(arguments[0])
return reject(arguments[0])
}
// daoValues = newValues
......@@ -95,6 +98,11 @@ Hooks.runHooks = function() {
}
run(hooks[tick])
}).spread(function () {
if (fn) {
fn.apply(self, [null].concat(Array.prototype.slice.apply(arguments)));
}
}, fn);
}
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')
, DataTypes = require(__dirname + '/data-types')
, SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface')
, Transaction = require(__dirname + '/transaction')
, Promise = require(__dirname + '/promise')
, QueryTypes = require('./query-types')
module.exports = (function() {
......@@ -87,10 +88,9 @@ module.exports = (function() {
logging: this.sequelize.options.logging
}, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") {
var chainer = new Utils.QueryChainer()
var promises = []
// For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
......@@ -98,12 +98,12 @@ module.exports = (function() {
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")) {
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) {
var chainer2 = new Utils.QueryChainer()
return Promise.all(promises).then(function(results) {
var promises = []
// Find the table that we're trying to create throgh DAOFactoryManager
, daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) { return dao.tableName === tableName })
, enumIdx = 0
......@@ -115,7 +115,7 @@ module.exports = (function() {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
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) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
......@@ -133,7 +133,7 @@ module.exports = (function() {
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++
......@@ -144,21 +144,16 @@ module.exports = (function() {
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']})
return Promise.all(promises).then(function() {
return queryAndEmit.call(self, sql, 'createTable', options)
})
.proxy(emitter, { events: ['error', 'sql']})
})
} else {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
queryAndEmit.call(self, sql, 'createTable', options)
.proxy(emitter, { events: ['success', 'error', 'sql']})
return queryAndEmit.call(self, sql, 'createTable', options)
}
}).run()
}
QueryInterface.prototype.dropTable = function(tableName, options) {
......@@ -169,10 +164,8 @@ module.exports = (function() {
var sql = this.QueryGenerator.dropTableQuery(tableName, options)
, self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql, 'dropTable'], options)
return queryAndEmit.call(this, sql, 'dropTable', options).then(function () {
var promises = []
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
......@@ -193,19 +186,16 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) {
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()
.success(function(results) {
emitter.emit('success', results[0])
self.emit('dropTable', null)
return Promise.all(promises).then(function (results) {
return results[0];
});
})
.proxy(emitter, { events: ['error', 'sql']})
}).run()
}
QueryInterface.prototype.dropAllTables = function(options) {
......@@ -472,10 +462,12 @@ module.exports = (function() {
}
QueryInterface.prototype.insert = function(dao, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes)
return queryAndEmit.call(this, [sql, dao, options], 'insert', {
success: function(obj) { obj.isNewRecord = false }
})
var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes);
return queryAndEmit.call(this, [sql, dao, options], 'insert').then(function (result) {
result.isNewRecord = false;
return result;
});
}
QueryInterface.prototype.bulkInsert = function(tableName, records, options, Model) {
......@@ -532,16 +524,20 @@ module.exports = (function() {
}
}
return new Utils.CustomEventEmitter(function(emitter) {
return new Promise(function (resolve, reject) {
var tick = 0
var iterate = function(err, i) {
if (!!err || i >= cascades.length) {
return run(err)
if (err) {
return reject(err)
}
if (i >= cascades.length) {
return resolve();
}
dao[cascades[i]]().success(function(tasks) {
if (tasks === null || tasks.length < 1) {
return run()
return resolve()
}
tasks = Array.isArray(tasks) ? tasks : [tasks]
......@@ -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) {
iterate(null, tick)
} else {
run()
resolve();
}
}).run()
}).then(function () {
return self.queryAndEmit([sql, dao, options], 'delete');
});
}
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
......@@ -865,7 +841,7 @@ module.exports = (function() {
}, options || {})
var execQuery = function(emitter) {
var query;
if (Array.isArray(sqlOrQueryParams)) {
if (sqlOrQueryParams.length === 1) {
sqlOrQueryParams.push(null)
......@@ -875,11 +851,22 @@ module.exports = (function() {
sqlOrQueryParams.push(typeof options === "object" ? options : {})
}
emitter.query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
} 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
.query
.success(function(obj) {
......@@ -897,12 +884,14 @@ module.exports = (function() {
emitter.emit('error', err)
}.bind(this))
.proxy(emitter, { events: ['sql'] })
return emitter;
}.bind(this)
if (!!emitter) {
execQuery(emitter)
} else {
return new Utils.CustomEventEmitter(execQuery).run()
return execQuery();
}
}
......
......@@ -10,6 +10,7 @@ var url = require("url")
, TransactionManager = require('./transaction-manager')
, QueryTypes = require('./query-types')
, 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:
......@@ -508,7 +509,7 @@ module.exports = (function() {
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() {
}
}
Sequelize.Promise = Promise
return Sequelize
})()
......@@ -606,5 +606,6 @@ Utils.col.prototype.toString = function (queryGenerator, parentModel) {
}
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.Promise = require(__dirname + "/promise")
Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo")
......@@ -60,7 +60,7 @@
"pg": "~2.8.1",
"watchr": "~2.4.11",
"chai": "~1.9.0",
"mocha": "~1.17.0",
"mocha": "~1.18.2",
"chai-datetime": "~1.1.1",
"sinon": "~1.8.1",
"mariasql": "0.1.20",
......
......@@ -4,6 +4,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, Sequelize = require('../../index')
, assert = require('assert')
chai.config.includeStack = true
......@@ -359,14 +360,17 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
user.destroy().then(function () {
assert(false);
}, function(err) {
expect(err).to.be.ok;
Task.findAll().success(function(tasks) {
expect(tasks).to.have.length(1)
done()
})
})
})
});
})
})
})
......
......@@ -1323,7 +1323,7 @@ describe(Support.getTestDialectTeaser("HasMany"), 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})
, Task = this.sequelize.define('Task', {}, {timestamps: false})
, WorkerTasks = this.sequelize.define('WorkerTasks', {}, {timestamps: false})
......@@ -1331,28 +1331,24 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
Worker.hasMany(Task, { through: WorkerTasks })
Task.hasMany(Worker, { through: WorkerTasks })
this.sequelize.sync().done(function(err) {
expect(err).not.to.be.ok
Worker.create({}).done(function (err, worker) {
expect(err).not.to.be.ok
Task.bulkCreate([{}, {}]).done(function (err) {
expect(err).not.to.be.ok
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()
})
})
})
})
})
})
})
// Test setup
return this.sequelize.sync().then(function() {
return Sequelize.Promise.all([
Worker.create({}),
Task.bulkCreate([{}, {}]).then(function () {
return Task.findAll()
})
]);
}).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() {
.then(function() { return b1.save() })
.then(function() { return a1.setRelation1(b1) })
.then(function() { return self.A.find({ where: { name: 'a1' } }) })
.done(function(a) {
.then(function(a) {
expect(a.relation1Id).to.be.eq(b1.id)
done()
})
......@@ -1414,7 +1410,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
.then(function() { return b1.save() })
.then(function() { return b1.setRelation1(a1) })
.then(function() { return self.B.find({ where: { name: 'b1' } }) })
.done(function(b) {
.then(function(b) {
expect(b.relation1Id).to.be.eq(a1.id)
done()
})
......
......@@ -1572,26 +1572,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('references', function() {
beforeEach(function(done) {
beforeEach(function() {
var self = this
this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
self.Author = self.sequelize.define('author', { firstName: Sequelize.STRING })
self.Author.sync().success(function() {
done()
})
})
this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING })
return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(function() {
return self.sequelize.getQueryInterface().dropTable('authors', { force: true })
}).then(function() {
return self.Author.sync()
})
})
afterEach(function(done) {
afterEach(function() {
var self = this
this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
done()
})
return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(function() {
return self.sequelize.getQueryInterface().dropTable('authors', { force: true })
})
})
......@@ -1680,7 +1677,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(2).to.equal(1)
done()
}
}).error(function(err) {
return;
}).catch(function(err) {
if (Support.dialectIsMySQL(true)) {
expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/)
} else if (dialect === 'mariadb') {
......
......@@ -8,6 +8,7 @@ var chai = require('chai')
, dialect = Support.getTestDialect()
, datetime = require('chai-datetime')
, _ = require('lodash')
, assert = require('assert')
chai.use(datetime)
chai.config.includeStack = true
......@@ -165,7 +166,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(err).not.to.be.ok
User.create({email: 'hello@sequelize.com'}).done(function (err) {
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.an.instanceof(Error)
done()
......
......@@ -507,6 +507,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
it('produce 3 errors', function(done) {
this.Project.create({}).error(function(err) {
expect(err).to.be.an.instanceOf(Error)
delete err.stack // longStackTraces
expect(Object.keys(err)).to.have.length(3)
done()
})
......
......@@ -2,8 +2,11 @@ var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, SequelizePromise = require(__dirname + "/../lib/promise")
, Promise = require('bluebird')
, dialect = Support.getTestDialect()
, _ = require('lodash')
, sinon = require('sinon')
chai.config.includeStack = true
......@@ -347,15 +350,231 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
done()
})
})
it('works for functions with only one return value', function (done) {
this.User
.find({ id: 1})
.spread(function(user) {
expect(user.id).to.equal(1)
expect(arguments.length).to.equal(1)
})
})
describe('backwards compat', function () {
it('should still work with .complete() when resolving', function(done) {
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()
})
})
})
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')
, DataTypes = require(__dirname + "/../lib/data-types")
, 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 = {
Sequelize: Sequelize,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!