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

Commit 3a56cf74 by Andy Edwards Committed by Sushant

feat(postgres): support ignoreDuplicates with ON CONFLICT DO NOTHING (#9883)

1 parent 4eeb9ab5
......@@ -36,8 +36,9 @@ AbstractDialect.prototype.supports = {
migrations: true,
upserts: true,
inserts: {
ignoreDuplicates: false, /* dialect specific words for INSERT IGNORE or DO NOTHING*/
updateOnDuplicate: false /* dialect specific words for ON DUPLICATE KEY UPDATE or ON CONFLICT*/
ignoreDuplicates: '', /* dialect specific words for INSERT IGNORE or DO NOTHING */
updateOnDuplicate: false, /* whether dialect supports ON DUPLICATE KEY UPDATE */
onConflictDoNothing: '' /* dialect specific words for ON CONFLICT DO NOTHING */
},
constraints: {
restrict: true,
......
......@@ -112,8 +112,8 @@ class QueryGenerator {
const bind = [];
const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;
let query;
let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)';
let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %>';
let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)<%= onConflictDoNothing %>';
let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %><%= onConflictDoNothing %>';
let outputFragment;
let identityWrapperRequired = false;
let tmpTable = ''; //tmpTable declaration for trigger
......@@ -234,6 +234,7 @@ class QueryGenerator {
const replacements = {
ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : '',
onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '',
table: this.quoteTable(table),
attributes: fields.join(','),
output: outputFragment,
......@@ -273,7 +274,7 @@ class QueryGenerator {
options = options || {};
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 serials = {};
const allAttributes = [];
......@@ -321,7 +322,8 @@ class QueryGenerator {
attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','),
tuples: tuples.join(','),
onDuplicateKeyUpdate,
returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : ''
returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : '',
onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : ''
};
return _.template(query, this._templateSettings)(replacements);
......
......@@ -40,6 +40,9 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy
using: 2,
where: true
},
inserts: {
onConflictDoNothing: ' ON CONFLICT DO NOTHING'
},
NUMERIC: true,
ARRAY: true,
RANGE: true,
......
......@@ -2480,7 +2480,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.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.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 {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.
......@@ -2506,7 +2506,7 @@ class Model {
const dialect = this.sequelize.options.dialect;
if (options.ignoreDuplicates && ['postgres', 'mssql'].includes(dialect)) {
if (options.ignoreDuplicates && ['mssql'].includes(dialect)) {
return Promise.reject(new Error(`${dialect} does not support the ignoreDuplicates option.`));
}
if (options.updateOnDuplicate && dialect !== 'mysql') {
......
......@@ -422,7 +422,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
});
if (current.dialect.supports.inserts.ignoreDuplicates) {
if (current.dialect.supports.inserts.ignoreDuplicates ||
current.dialect.supports.inserts.onConflictDoNothing) {
it('should support the ignoreDuplicates option', function() {
const self = this;
const data = [
......
......@@ -572,12 +572,24 @@ if (dialect.match(/^postgres/)) {
bind: ['foo']
}
}, {
arguments: ['myTable', {name: 'foo'}, {}, { ignoreDuplicates: true }],
expectation: {
query: 'INSERT INTO "myTable" ("name") VALUES ($1) ON CONFLICT DO NOTHING;',
bind: ['foo']
}
}, {
arguments: ['myTable', {name: 'foo'}, {}, { returning: true }],
expectation: {
query: 'INSERT INTO "myTable" ("name") VALUES ($1) RETURNING *;',
bind: ['foo']
}
}, {
arguments: ['myTable', {name: 'foo'}, {}, { ignoreDuplicates: true, returning: true }],
expectation: {
query: 'INSERT INTO "myTable" ("name") VALUES ($1) ON CONFLICT DO NOTHING RETURNING *;',
bind: ['foo']
}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: {
query: 'INSERT INTO "myTable" ("name") VALUES ($1);',
......@@ -758,9 +770,15 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', [{name: 'foo'}, {name: '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 }],
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'}]],
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!