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

Commit 07f70454 by Mick Hansen

Merge pull request #2917 from mbroadst/return-values-tests

refactor support for return values on insert/update
2 parents 6817aad8 464ef298
...@@ -9,12 +9,13 @@ AbstractDialect.prototype.supports = { ...@@ -9,12 +9,13 @@ AbstractDialect.prototype.supports = {
'DEFAULT VALUES': false, 'DEFAULT VALUES': false,
'VALUES ()': false, 'VALUES ()': false,
'LIMIT ON UPDATE': false, 'LIMIT ON UPDATE': false,
'ON DUPLICATE KEY': true,
/* does the dialect support returning values for inserted/updated fields using RETURNING */ /* What is the dialect's keyword for INSERT IGNORE */
'RETURNING': false, 'IGNORE': '',
/* does the dialect support returning values for inserted/updated fields using OUTPUT */ /* does the dialect support returning values for inserted/updated fields */
'OUTPUT': false, returnValues: false,
/* features specific to autoIncrement values */ /* features specific to autoIncrement values */
autoIncrement: { autoIncrement: {
...@@ -27,10 +28,7 @@ AbstractDialect.prototype.supports = { ...@@ -27,10 +28,7 @@ AbstractDialect.prototype.supports = {
/* does the dialect support updating autoincrement fields */ /* does the dialect support updating autoincrement fields */
update: true update: true
}, },
'ON DUPLICATE KEY': true,
/* What is the dialect's keyword for INSERT IGNORE */
'IGNORE': '',
schemas: false, schemas: false,
transactions: true, transactions: true,
migrations: true, migrations: true,
......
...@@ -190,13 +190,13 @@ module.exports = (function() { ...@@ -190,13 +190,13 @@ module.exports = (function() {
emptyQuery += ' VALUES ()'; emptyQuery += ' VALUES ()';
} }
// FIXME: ideally these two can be merged in the future, the only if (this._dialect.supports.returnValues && options.returning) {
// difference is placement of the value in the query if (!!this._dialect.supports.returnValues.returning) {
if (this._dialect.supports['RETURNING'] && options.returning) { valueQuery += ' RETURNING *';
valueQuery += ' RETURNING *'; emptyQuery += ' RETURNING *';
emptyQuery += ' RETURNING *'; } else if (!!this._dialect.supports.returnValues.output) {
} else if (this._dialect.supports['OUTPUT'] && options.returning) { outputFragment = ' OUTPUT INSERTED.*';
outputFragment = ' OUTPUT INSERTED.*'; }
} }
if (this._dialect.supports['EXCEPTION'] && options.exception) { if (this._dialect.supports['EXCEPTION'] && options.exception) {
...@@ -303,10 +303,13 @@ module.exports = (function() { ...@@ -303,10 +303,13 @@ module.exports = (function() {
query += ' LIMIT ' + this.escape(options.limit) + ' '; query += ' LIMIT ' + this.escape(options.limit) + ' ';
} }
if (this._dialect.supports['RETURNING'] && options.returning) { if (this._dialect.supports.returnValues) {
query += ' RETURNING *'; if (!!this._dialect.supports.returnValues.output) {
} else if (this._dialect.supports['OUTPUT']) { // we always need this for mssql
outputFragment = ' OUTPUT INSERTED.*'; outputFragment = ' OUTPUT INSERTED.*';
} else if (this._dialect.supports.returnValues && options.returning) {
query += ' RETURNING *';
}
} }
if (attributes) { if (attributes) {
...@@ -388,11 +391,16 @@ module.exports = (function() { ...@@ -388,11 +391,16 @@ module.exports = (function() {
var query var query
, key , key
, value , value
, values = []; , values = []
, outputFragment;
query = 'UPDATE <%= table %> SET <%= values %> WHERE <%= where %>'; query = 'UPDATE <%= table %> SET <%= values %><%= output %> WHERE <%= where %>';
if (this._dialect.supports['RETURNING']) { if (this._dialect.supports.returnValues) {
query += ' RETURNING *'; if (!!this._dialect.supports.returnValues.returning) {
query += ' RETURNING *';
} else if (!!this._dialect.supports.returnValues.output) {
outputFragment = ' OUTPUT INSERTED.*';
}
} }
for (key in attrValueHash) { for (key in attrValueHash) {
...@@ -409,6 +417,7 @@ module.exports = (function() { ...@@ -409,6 +417,7 @@ module.exports = (function() {
var replacements = { var replacements = {
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
values: values.join(','), values: values.join(','),
output: outputFragment,
where: this.getWhereConditions(where) where: this.getWhereConditions(where)
}; };
......
...@@ -12,8 +12,6 @@ var MssqlDialect = function(sequelize) { ...@@ -12,8 +12,6 @@ var MssqlDialect = function(sequelize) {
}; };
MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), { MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), {
'RETURNING': false,
'OUTPUT': true,
'DEFAULT': true, 'DEFAULT': true,
'DEFAULT VALUES': true, 'DEFAULT VALUES': true,
'LIMIT ON UPDATE': true, 'LIMIT ON UPDATE': true,
...@@ -21,6 +19,9 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.support ...@@ -21,6 +19,9 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.support
transactions: false, transactions: false,
migrations: false, migrations: false,
upserts: false, upserts: false,
returnValues: {
output: true
},
autoIncrement: { autoIncrement: {
identityInsert: true, identityInsert: true,
defaultValue: false, defaultValue: false,
......
...@@ -196,12 +196,17 @@ module.exports = (function() { ...@@ -196,12 +196,17 @@ module.exports = (function() {
}, },
bulkInsertQuery: function(tableName, attrValueHashes, options, attributes) { bulkInsertQuery: function(tableName, attrValueHashes, options, attributes) {
var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;' var query = 'INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>;'
, emptyQuery = 'INSERT INTO <%= table %> DEFAULT VALUES' , emptyQuery = 'INSERT INTO <%= table %><%= output %> DEFAULT VALUES'
, tuples = [] , tuples = []
, allAttributes = [] , allAttributes = []
, needIdentityInsertWrapper = false , needIdentityInsertWrapper = false
, allQueries = []; , allQueries = []
, outputFragment;
if (options.returning) {
outputFragment = ' OUTPUT INSERTED.*';
}
Utils._.forEach(attrValueHashes, function(attrValueHash, i) { Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
// special case for empty objects with primary keys // special case for empty objects with primary keys
...@@ -243,7 +248,8 @@ module.exports = (function() { ...@@ -243,7 +248,8 @@ module.exports = (function() {
attributes: allAttributes.map(function(attr) { attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr); return this.quoteIdentifier(attr);
}.bind(this)).join(','), }.bind(this)).join(','),
tuples: tuples tuples: tuples,
output: outputFragment
}; };
var generatedQuery = Utils._.template(allQueries.join(';'))(replacements); var generatedQuery = Utils._.template(allQueries.join(';'))(replacements);
......
...@@ -114,12 +114,16 @@ module.exports = (function() { ...@@ -114,12 +114,16 @@ module.exports = (function() {
this.handleInsertQuery(data); this.handleInsertQuery(data);
if (!this.callee) { if (!this.callee) {
// NOTE: super contrived. This just passes the newly added query-interface if (this.options.plain) {
// test returning only the PK. There isn't a way in MSSQL to identify // NOTE: super contrived. This just passes the newly added query-interface
// that a given return value is the PK, and we have no schema information // test returning only the PK. There isn't a way in MSSQL to identify
// because there was no calling Model. // that a given return value is the PK, and we have no schema information
var record = data[0]; // because there was no calling Model.
result = record[Object.keys(record)[0]]; var record = data[0];
result = record[Object.keys(record)[0]];
} else {
result = data;
}
} }
} }
......
...@@ -12,10 +12,12 @@ var PostgresDialect = function(sequelize) { ...@@ -12,10 +12,12 @@ var PostgresDialect = function(sequelize) {
}; };
PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), { PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), {
'RETURNING': true,
'DEFAULT VALUES': true, 'DEFAULT VALUES': true,
'EXCEPTION': true, 'EXCEPTION': true,
'ON DUPLICATE KEY': false, 'ON DUPLICATE KEY': false,
returnValues: {
returning: true
},
schemas: true, schemas: true,
lock: true, lock: true,
forShare: 'FOR SHARE', forShare: 'FOR SHARE',
......
...@@ -354,7 +354,7 @@ module.exports = (function() { ...@@ -354,7 +354,7 @@ module.exports = (function() {
, serials = [] , serials = []
, allAttributes = []; , allAttributes = [];
if (this._dialect.supports['RETURNING'] && options.returning) { if (options.returning) {
query += ' RETURNING *'; query += ' RETURNING *';
} }
......
...@@ -459,7 +459,7 @@ module.exports = (function() { ...@@ -459,7 +459,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 && dao.Model.rawAttributes, options); var sql = this.QueryGenerator.insertQuery(tableName, values, dao && dao.Model.rawAttributes, options);
options.type = 'INSERT'; options.type = QueryTypes.INSERT;
return this.sequelize.query(sql, dao, options).then(function(result) { return this.sequelize.query(sql, dao, options).then(function(result) {
if (dao) result.isNewRecord = false; if (dao) result.isNewRecord = false;
return result; return result;
...@@ -536,6 +536,7 @@ module.exports = (function() { ...@@ -536,6 +536,7 @@ module.exports = (function() {
}; };
QueryInterface.prototype.bulkInsert = function(tableName, records, options, attributes) { QueryInterface.prototype.bulkInsert = function(tableName, records, options, attributes) {
options.type = QueryTypes.INSERT;
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes); var sql = this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes);
return this.sequelize.query(sql, null, options); return this.sequelize.query(sql, null, options);
}; };
......
...@@ -449,27 +449,20 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -449,27 +449,20 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
} }
if (current.dialect.supports['RETURNING']) { if (current.dialect.supports.returnValues) {
describe('Autoincrement values', function () { describe('return values', function () {
it('should make the autoincremented values available on the returned instances', function () { it('should make the autoincremented values available on the returned instances', function () {
var User = this.sequelize.define('user', {}); var User = this.sequelize.define('user', {});
return User.sync({force: true}).then(function () { return User.sync({force: true}).then(function () {
return User.bulkCreate([ return User.create({}, {returning: true}).then(function (user) {
{}, expect(user.get('id')).to.be.ok;
{}, expect(user.get('id')).to.equal(1);
{}
], {returning: true}).then(function (users) {
expect(users.length).to.be.ok;
users.forEach(function (user, i) {
expect(user.get('id')).to.be.ok;
expect(user.get('id')).to.equal(i+1);
});
}); });
}); });
}); });
it('should make the autoincremented values available on the returned instances', function () { it('should make the autoincremented values available on the returned instances with custom fields', function () {
var User = this.sequelize.define('user', { var User = this.sequelize.define('user', {
maId: { maId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
...@@ -480,16 +473,9 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -480,16 +473,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
return User.sync({force: true}).then(function () { return User.sync({force: true}).then(function () {
return User.bulkCreate([ return User.create({}, {returning: true}).then(function (user) {
{}, expect(user.get('maId')).to.be.ok;
{}, expect(user.get('maId')).to.equal(1);
{}
], {returning: true}).then(function (users) {
expect(users.length).to.be.ok;
users.forEach(function (user, i) {
expect(user.get('maId')).to.be.ok;
expect(user.get('maId')).to.equal(i+1);
});
}); });
}); });
}); });
...@@ -1506,6 +1492,53 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -1506,6 +1492,53 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
} }
if (current.dialect.supports.returnValues) {
describe('return values', function () {
it('should make the autoincremented values available on the returned instances', function () {
var User = this.sequelize.define('user', {});
return User.sync({force: true}).then(function () {
return User.bulkCreate([
{},
{},
{}
], {returning: true}).then(function (users) {
expect(users.length).to.be.ok;
users.forEach(function (user, i) {
expect(user.get('id')).to.be.ok;
expect(user.get('id')).to.equal(i+1);
});
});
});
});
it('should make the autoincremented values available on the returned instances with custom fields', function () {
var User = this.sequelize.define('user', {
maId: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
field: 'yo_id'
}
});
return User.sync({force: true}).then(function () {
return User.bulkCreate([
{},
{},
{}
], {returning: true}).then(function (users) {
expect(users.length).to.be.ok;
users.forEach(function (user, i) {
expect(user.get('maId')).to.be.ok;
expect(user.get('maId')).to.equal(i+1);
});
});
});
});
});
}
describe('enums', function() { describe('enums', function() {
it('correctly restores enum values', function(done) { it('correctly restores enum values', function(done) {
var self = this var self = this
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!