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

Commit 0e53c4b7 by Mick Hansen

Merge pull request #1624 from seegno/fix-bulk-operations-result

Change bulkUpdate to return the affected rows
2 parents ce35cca2 8198aa09
...@@ -398,7 +398,13 @@ module.exports = { ...@@ -398,7 +398,13 @@ module.exports = {
UUIDV4: 'UUIDV4', UUIDV4: 'UUIDV4',
/** /**
* A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model * A key / value column. Only available in postgres.
* @property HSTORE
*/
HSTORE: 'HSTORE',
/**
* A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model
* that is returned to the user but not stored in the DB. * that is returned to the user but not stored in the DB.
* *
* You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example: * You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example:
...@@ -453,22 +459,5 @@ module.exports = { ...@@ -453,22 +459,5 @@ module.exports = {
* An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres. * An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres.
* @property ARRAY * @property ARRAY
*/ */
ARRAY: function(type) { return type + '[]'; }, ARRAY: function(type) { return type + '[]'; }
/**
* A key / value column. Only available in postgres.
* @property HSTORE
*/
get HSTORE() {
var result = function() {
return {
type: 'HSTORE'
};
};
result.type = 'HSTORE';
result.toString = result.valueOf = function() { return 'HSTORE'; };
return result;
}
}; };
...@@ -143,7 +143,9 @@ module.exports = (function() { ...@@ -143,7 +143,9 @@ module.exports = (function() {
/* /*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs. Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
*/ */
insertQuery: function(table, valueHash, modelAttributes) { insertQuery: function(table, valueHash, modelAttributes, options) {
options = options || {};
var query var query
, valueQuery = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>)' , valueQuery = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>)'
, emptyQuery = 'INSERT INTO <%= table %>' , emptyQuery = 'INSERT INTO <%= table %>'
...@@ -168,7 +170,7 @@ module.exports = (function() { ...@@ -168,7 +170,7 @@ module.exports = (function() {
emptyQuery += ' VALUES ()'; emptyQuery += ' VALUES ()';
} }
if (this._dialect.supports['RETURNING']) { if (this._dialect.supports['RETURNING'] && options.returning) {
valueQuery += ' RETURNING *'; valueQuery += ' RETURNING *';
emptyQuery += ' RETURNING *'; emptyQuery += ' RETURNING *';
} }
...@@ -235,7 +237,7 @@ module.exports = (function() { ...@@ -235,7 +237,7 @@ module.exports = (function() {
query += ' LIMIT ' + this.escape(options.limit) + ' '; query += ' LIMIT ' + this.escape(options.limit) + ' ';
} }
if (this._dialect.supports['RETURNING'] && (options.returning || options.returning === undefined)) { if (this._dialect.supports['RETURNING'] && options.returning) {
query += ' RETURNING *'; query += ' RETURNING *';
} }
......
...@@ -255,11 +255,17 @@ module.exports = (function() { ...@@ -255,11 +255,17 @@ module.exports = (function() {
}, },
bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) { bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) {
var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;' options = options || {};
var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>'
, tuples = [] , tuples = []
, serials = [] , serials = []
, allAttributes = []; , allAttributes = [];
if (this._dialect.supports['RETURNING'] && options.returning) {
query += ' RETURNING *';
}
Utils._.forEach(attrValueHashes, function(attrValueHash) { Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) { Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) { if (allAttributes.indexOf(key) === -1) {
...@@ -291,6 +297,8 @@ module.exports = (function() { ...@@ -291,6 +297,8 @@ module.exports = (function() {
, tuples: tuples.join(',') , tuples: tuples.join(',')
}; };
query = query + ';';
return Utils._.template(query)(replacements); return Utils._.template(query)(replacements);
}, },
...@@ -815,7 +823,7 @@ module.exports = (function() { ...@@ -815,7 +823,7 @@ module.exports = (function() {
if (value && value._isSequelizeMethod) { if (value && value._isSequelizeMethod) {
return value.toString(this); return value.toString(this);
} else { } else {
if (field && field.type && field.type.toString() === DataTypes.HSTORE.type && Utils._.isObject(value)) { if (Utils._.isObject(value) && field && (field === DataTypes.HSTORE || field.type === DataTypes.HSTORE)) {
value = hstore.stringify(value); value = hstore.stringify(value);
} }
......
...@@ -7,6 +7,16 @@ var Utils = require('../../utils') ...@@ -7,6 +7,16 @@ var Utils = require('../../utils')
, QueryTypes = require('../../query-types') , QueryTypes = require('../../query-types')
, Promise = require('../../promise'); , Promise = require('../../promise');
// Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT.
var parseHstoreFields = function(model, row) {
Utils._.keys(row).forEach(function(key) {
if (model._isHstoreAttribute(key)) {
row[key] = hstore.parse(row[key]);
}
});
};
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
this.client = client; this.client = client;
...@@ -125,15 +135,9 @@ module.exports = (function() { ...@@ -125,15 +135,9 @@ 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 (!!self.callee && !!self.callee._hasHstoreAttributes) { if (!!self.callee && !!self.callee._hasHstoreAttributes) {
rows.forEach(function(row) { rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) { parseHstoreFields(self.callee, row);
if (self.callee._isHstoreAttribute(key)) {
row[key] = hstore.parse(row[key]);
}
});
}); });
} }
...@@ -141,20 +145,34 @@ module.exports = (function() { ...@@ -141,20 +145,34 @@ module.exports = (function() {
} }
} else if (self.send('isShowOrDescribeQuery')) { } else if (self.send('isShowOrDescribeQuery')) {
return results; return results;
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) { } else if (QueryTypes.BULKUPDATE === self.options.type) {
if (!self.options.returning) {
return result.rowCount;
}
if (!!self.callee && !!self.callee._hasHstoreAttributes) {
rows.forEach(function(row) {
parseHstoreFields(self.callee, row);
});
}
return self.send('handleSelectQuery', rows);
} else if (QueryTypes.BULKDELETE === self.options.type) {
return result.rowCount; return result.rowCount;
} else if (self.send('isInsertQuery') || self.send('isUpdateQuery')) { } else if (self.send('isInsertQuery') || self.send('isUpdateQuery')) {
if (self.callee !== null) { // may happen for bulk inserts or bulk updates if (!!self.callee && self.callee.dataValues) {
if (!!self.callee.Model && !!self.callee.Model._hasHstoreAttributes) {
parseHstoreFields(self.callee.Model, rows[0]);
}
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 (!!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);
}
var attr = Utils._.find(self.callee.Model.rawAttributes, function (attribute) { var attr = Utils._.find(self.callee.Model.rawAttributes, function (attribute) {
return attribute.fieldName === key || attribute.field === key; return attribute.fieldName === key || attribute.field === key;
}); });
self.callee.dataValues[attr && attr.fieldName || key] = record; self.callee.dataValues[attr && attr.fieldName || key] = record;
} }
} }
......
...@@ -262,12 +262,13 @@ module.exports = (function() { ...@@ -262,12 +262,13 @@ module.exports = (function() {
this.Instance.prototype.validators = {}; this.Instance.prototype.validators = {};
Utils._.each(this.rawAttributes, function(definition, name) { Utils._.each(this.rawAttributes, function(definition, name) {
var type = definition.originalType || definition.type || definition; var type = definition.originalType || definition.type || definition;
if (type === DataTypes.BOOLEAN) { if (type === DataTypes.BOOLEAN) {
self._booleanAttributes.push(name); self._booleanAttributes.push(name);
} else if (type === DataTypes.DATE) { } else if (type === DataTypes.DATE) {
self._dateAttributes.push(name); self._dateAttributes.push(name);
} else if (type === DataTypes.HSTORE.type) { } else if (type === DataTypes.HSTORE) {
self._hstoreAttributes.push(name); self._hstoreAttributes.push(name);
} else if (type === DataTypes.VIRTUAL) { } else if (type === DataTypes.VIRTUAL) {
self._virtualAttributes.push(name); self._virtualAttributes.push(name);
...@@ -1341,6 +1342,7 @@ module.exports = (function() { ...@@ -1341,6 +1342,7 @@ module.exports = (function() {
* @param {Boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation * @param {Boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation
* @param {Boolean} [options.hooks=true] Run before / after bulk update hooks? * @param {Boolean} [options.hooks=true] Run before / after bulk update hooks?
* @param {Boolean} [options.individualHooks=false] Run before / after update hooks? * @param {Boolean} [options.individualHooks=false] Run before / after update hooks?
* @param {Boolean} [options.returning=false] Return the affected rows (only for postgres)
* @param {Number} [options.limit] How many rows to update (only for mysql and mariadb) * @param {Number} [options.limit] How many rows to update (only for mysql and mariadb)
* @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API * @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API
* *
...@@ -1353,6 +1355,7 @@ module.exports = (function() { ...@@ -1353,6 +1355,7 @@ module.exports = (function() {
validate: true, validate: true,
hooks: true, hooks: true,
individualHooks: false, individualHooks: false,
returning: false,
force: false force: false
}, options || {}); }, options || {});
...@@ -1458,6 +1461,10 @@ module.exports = (function() { ...@@ -1458,6 +1461,10 @@ module.exports = (function() {
// Run query to update all rows // Run query to update all rows
return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.tableAttributes).then(function(affectedRows) { return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.tableAttributes).then(function(affectedRows) {
if (options.returning) {
return [affectedRows.length, affectedRows];
}
return [affectedRows]; return [affectedRows];
}); });
}).tap(function(result) { }).tap(function(result) {
......
...@@ -414,7 +414,7 @@ module.exports = (function() { ...@@ -414,7 +414,7 @@ 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, options);
return this.sequelize.query(sql, dao, options).then(function(result) { return this.sequelize.query(sql, dao, options).then(function(result) {
result.isNewRecord = false; result.isNewRecord = false;
...@@ -448,9 +448,11 @@ module.exports = (function() { ...@@ -448,9 +448,11 @@ module.exports = (function() {
}; };
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier, options, attributes) { QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier, options, attributes) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes); var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes)
, table = Utils._.isObject(tableName) ? tableName : { tableName: tableName }
, daoTable = Utils._.find(this.sequelize.daoFactoryManager.daos, { tableName: table.tableName });
return this.sequelize.query(sql, null, options); return this.sequelize.query(sql, daoTable, options);
}; };
QueryInterface.prototype.delete = function(dao, tableName, identifier, options) { QueryInterface.prototype.delete = function(dao, tableName, identifier, options) {
......
...@@ -805,6 +805,32 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -805,6 +805,32 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
if (dialect === "postgres") {
it('returns the affected rows if `options.returning` is true', function(_done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
, done = _.after(2, _done)
this.User.bulkCreate(data).success(function() {
self.User.update({ username: 'Bill' }, { secretValue: '42' }, { returning: true }).spread(function(count, rows) {
expect(count).to.equal(2)
expect(rows).to.have.length(2)
done()
})
self.User.update({ username: 'Bill'}, { secretValue: '44' }, { returning: true }).spread(function(count, rows) {
expect(count).to.equal(0)
expect(rows).to.have.length(0)
done()
})
})
})
}
if(Support.dialectIsMySQL()) { if(Support.dialectIsMySQL()) {
it('supports limit clause', function (done) { it('supports limit clause', function (done) {
var self = this var self = this
......
...@@ -260,7 +260,7 @@ if (dialect.match(/^postgres/)) { ...@@ -260,7 +260,7 @@ if (dialect.match(/^postgres/)) {
.create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}}) .create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.success(function(newUser) { .success(function(newUser) {
// Check to see if the default value for an hstore field works // Check to see if the default value for an hstore field works
expect(newUser.document).to.deep.equal({default: 'value'}) expect(newUser.document).to.deep.equal({ default: 'value' })
expect(newUser.settings).to.deep.equal({ created: { test: '"value"' }}) expect(newUser.settings).to.deep.equal({ created: { test: '"value"' }})
// Check to see if updating an hstore field works // Check to see if updating an hstore field works
...@@ -295,6 +295,22 @@ if (dialect.match(/^postgres/)) { ...@@ -295,6 +295,22 @@ if (dialect.match(/^postgres/)) {
.error(console.log) .error(console.log)
}) })
it("should update hstore correctly and return the affected rows", function(done) {
var self = this
this.User
.create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}})
.success(function(oldUser) {
// Update the user and check that the returned object's fields have been parsed by the hstore library
self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, oldUser.identifiers, { returning: true }).spread(function(count, users) {
expect(count).to.equal(1);
expect(users[0].settings).to.deep.equal({should: 'update', to: 'this', first: 'place'})
done()
})
})
.error(console.log)
})
it("should read hstore correctly", function(done) { it("should read hstore correctly", function(done) {
var self = this var self = this
var data = { username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}} var data = { username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}}
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!