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

Commit 35b69a3e by Mick Hansen

feat(pg): findOrCreate now returns the error without killing the transaction

1 parent be69a06e
......@@ -202,7 +202,14 @@ module.exports = (function() {
if (this._dialect.supports['EXCEPTION'] && options.exception) {
// Mostly for internal use, so we expect the user to know what he's doing!
// pg_temp functions are private per connection, so we never risk this function interfering with another one.
// <= 9.1
options.exception = 'WHEN unique_violation THEN NULL;';
valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc() RETURNS SETOF <%= table %> AS $body$ BEGIN RETURN QUERY ' + valueQuery + '; EXCEPTION ' + options.exception + ' END; $body$ LANGUAGE plpgsql; SELECT * FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc();';
// >= 9.2
options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;';
valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response <%= table %>, OUT sequelize_caught_exception text) RETURNS RECORD AS $$ BEGIN ' + valueQuery + ' INTO response; EXCEPTION ' + options.exception + ' END $$ LANGUAGE plpgsql; SELECT (testfunc.response).*, testfunc.sequelize_caught_exception FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc()';
}
if (this._dialect.supports['ON DUPLICATE KEY'] && options.onDuplicate) {
......
......@@ -91,6 +91,13 @@ module.exports = (function() {
return results;
}
if (rows[0] && rows[0].sequelize_caught_exception) {
throw new self.formatError({
code: '23505',
detail: rows[0].sequelize_caught_exception
});
}
if (self.isShowIndexesQuery()) {
results.forEach(function (result) {
var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
......
......@@ -1148,10 +1148,7 @@ module.exports = (function() {
values = Utils._.defaults(values, options.where);
}
// VERY PG specific
// If a unique constraint is triggered inside a transaction, we cannot execute further queries inside that transaction, short of rolling back or committing.
// To circumwent this, we add an EXCEPTION WHEN unique_violation clause, which always returns an empty result set
queryOptions.exception = 'WHEN unique_violation THEN RETURN QUERY SELECT * FROM <%= table %> WHERE 1 <> 1;';
queryOptions.exception = true;
return self.create(values, queryOptions).bind(this).then(function(instance) {
if (instance.get(self.primaryKeyAttribute, { raw: true }) === null) {
......
......@@ -91,14 +91,19 @@ describe(Support.getTestDialectTeaser('Model'), function() {
username: 'gottlieb'
});
}).then(function () {
return expect(User.findOrCreate({
return User.findOrCreate({
where: {
objectId: 'asdasdasd'
},
defaults: {
username: 'gottlieb'
}
})).to.be.rejectedWith(Sequelize.UniqueConstraintError);
}).then(function () {
throw new Error('I should have ben rejected');
}, function (err) {
expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok;
expect(err.fields).to.be.ok;
});
});
});
......@@ -222,6 +227,54 @@ describe(Support.getTestDialectTeaser('Model'), function() {
});
}
it('should error correctly when defaults contain a unique key', function () {
var User = this.sequelize.define('user', {
objectId: {
type: DataTypes.STRING,
unique: true
},
username: {
type: DataTypes.STRING,
unique: true
}
});
return User.sync({force: true}).then(function () {
return User.create({
username: 'gottlieb'
});
}).then(function () {
return Promise.join(
User.findOrCreate({
where: {
objectId: 'asdasdasd'
},
defaults: {
username: 'gottlieb'
}
}).then(function () {
throw new Error('I should have ben rejected');
}, function (err) {
expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok;
expect(err.fields).to.be.ok;
}),
User.findOrCreate({
where: {
objectId: 'asdasdasd'
},
defaults: {
username: 'gottlieb'
}
}).then(function () {
throw new Error('I should have ben rejected');
}, function (err) {
expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok;
expect(err.fields).to.be.ok;
})
);
});
});
// Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off
(dialect !== 'sqlite' ? it : it.skip)('works without a transaction', function() {
return Promise.join(
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!