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

You need to sign in or sign up before continuing.
Commit 464ef298 by Matt Broadstone

refactor support for return values on insert/update

The dialect support for return values has been refactored
to make detecting it more readable for the two dialects
that currently support it. Tests have also been added for
regular creates as well as bulkCreates for returning these
values
1 parent a4b90d40
...@@ -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!