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

Commit 4e439fa5 by Justin Hinerman Committed by Sushant

feat(postgres): support ignoreDuplicates for postges >= 9.5 (#9954)

1 parent e26f62f2
...@@ -41,6 +41,8 @@ AbstractDialect.prototype.supports = { ...@@ -41,6 +41,8 @@ AbstractDialect.prototype.supports = {
}, },
migrations: true, migrations: true,
upserts: true, upserts: true,
/* dialect specific words for ON CONFLICT DO NOTHING */
onConflictDoNothing: '',
constraints: { constraints: {
restrict: true, restrict: true,
addConstraint: true, addConstraint: true,
......
...@@ -82,8 +82,8 @@ const QueryGenerator = { ...@@ -82,8 +82,8 @@ const QueryGenerator = {
const fields = []; const fields = [];
const values = []; const values = [];
let query; let query;
let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)'; let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)<%= onConflictDoNothing %>';
let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %>'; let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %><%= onConflictDoNothing %>';
let outputFragment; let outputFragment;
let identityWrapperRequired = false; let identityWrapperRequired = false;
let tmpTable = ''; //tmpTable declaration for trigger let tmpTable = ''; //tmpTable declaration for trigger
...@@ -193,6 +193,7 @@ const QueryGenerator = { ...@@ -193,6 +193,7 @@ const QueryGenerator = {
const replacements = { const replacements = {
ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.IGNORE : '', ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.IGNORE : '',
onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.onConflictDoNothing : '',
table: this.quoteTable(table), table: this.quoteTable(table),
attributes: fields.join(','), attributes: fields.join(','),
output: outputFragment, output: outputFragment,
...@@ -221,7 +222,7 @@ const QueryGenerator = { ...@@ -221,7 +222,7 @@ const QueryGenerator = {
options = options || {}; options = options || {};
fieldMappedAttributes = fieldMappedAttributes || {}; fieldMappedAttributes = fieldMappedAttributes || {};
const query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= returning %>;'; const query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= onConflictDoNothing %><%= returning %>;';
const tuples = []; const tuples = [];
const serials = {}; const serials = {};
const allAttributes = []; const allAttributes = [];
...@@ -269,7 +270,8 @@ const QueryGenerator = { ...@@ -269,7 +270,8 @@ const QueryGenerator = {
attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','), attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','),
tuples: tuples.join(','), tuples: tuples.join(','),
onDuplicateKeyUpdate, onDuplicateKeyUpdate,
returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : '' returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : '',
onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.onConflictDoNothing : ''
}; };
return _.template(query, this._templateSettings)(replacements); return _.template(query, this._templateSettings)(replacements);
......
...@@ -40,6 +40,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy ...@@ -40,6 +40,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy
using: 2, using: 2,
where: true where: true
}, },
onConflictDoNothing: ' ON CONFLICT DO NOTHING',
NUMERIC: true, NUMERIC: true,
ARRAY: true, ARRAY: true,
RANGE: true, RANGE: true,
......
...@@ -2313,7 +2313,7 @@ class Model { ...@@ -2313,7 +2313,7 @@ class Model {
* @param {Boolean} [options.validate=false] 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=false] 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 create hooks? * @param {Boolean} [options.hooks=true] Run before / after bulk create hooks?
* @param {Boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true. * @param {Boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true.
* @param {Boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by postgres) * @param {Boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by postgres < 9.5)
* @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by mysql). By default, all fields are updated. * @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by mysql). By default, all fields are updated.
* @param {Transaction} [options.transaction] Transaction to run query under * @param {Transaction} [options.transaction] Transaction to run query under
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
...@@ -2338,7 +2338,8 @@ class Model { ...@@ -2338,7 +2338,8 @@ class Model {
options.fields = options.fields || Object.keys(this.tableAttributes); options.fields = options.fields || Object.keys(this.tableAttributes);
const dialect = this.sequelize.options.dialect; const dialect = this.sequelize.options.dialect;
if (options.ignoreDuplicates && ['postgres', 'mssql'].indexOf(dialect) !== -1) {
if (options.ignoreDuplicates && dialect === 'mssql') {
return Promise.reject(new Error(dialect + ' does not support the \'ignoreDuplicates\' option.')); return Promise.reject(new Error(dialect + ' does not support the \'ignoreDuplicates\' option.'));
} }
if (options.updateOnDuplicate && dialect !== 'mysql') { if (options.updateOnDuplicate && dialect !== 'mysql') {
......
...@@ -394,7 +394,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -394,7 +394,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
}); });
if (current.dialect.supports.ignoreDuplicates) { if (current.dialect.supports.ignoreDuplicates ||
current.dialect.supports.onConflictDoNothing) {
it('should support the ignoreDuplicates option', function() { it('should support the ignoreDuplicates option', function() {
const self = this; const self = this;
const data = [ const data = [
...@@ -580,7 +581,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -580,7 +581,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return Maya.bulkCreate([M2]); return Maya.bulkCreate([M2]);
}).spread(m => { }).spread(m => {
// only attributes are returned, no fields are mixed // only attributes are returned, no fields are mixed
expect(m.createdAt).to.be.ok; expect(m.createdAt).to.be.ok;
expect(m.created_at).to.not.exist; expect(m.created_at).to.not.exist;
expect(m.secret_given).to.not.exist; expect(m.secret_given).to.not.exist;
......
...@@ -558,9 +558,15 @@ if (dialect.match(/^postgres/)) { ...@@ -558,9 +558,15 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo');" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo');"
}, { }, {
arguments: ['myTable', {name: 'foo'}, {}, { ignoreDuplicates: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') ON CONFLICT DO NOTHING;"
}, {
arguments: ['myTable', {name: 'foo'}, {}, { returning: true }], arguments: ['myTable', {name: 'foo'}, {}, { returning: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo'}, {}, { ignoreDuplicates: true, returning: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') ON CONFLICT DO NOTHING RETURNING *;"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;');" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;');"
}, { }, {
...@@ -666,9 +672,15 @@ if (dialect.match(/^postgres/)) { ...@@ -666,9 +672,15 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar');" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar');"
}, { }, {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], { ignoreDuplicates: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') ON CONFLICT DO NOTHING;"
}, {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], { returning: true }], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], { returning: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], { ignoreDuplicates: true, returning: true }],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') ON CONFLICT DO NOTHING RETURNING *;"
}, {
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');"
}, { }, {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!