Skip to content

Instantly share code, notes, and snippets.

@fnimick
Created June 9, 2020 14:31
Show Gist options
  • Save fnimick/313091df3ecc6c5e5f17be1e0950c33f to your computer and use it in GitHub Desktop.
Save fnimick/313091df3ecc6c5e5f17be1e0950c33f to your computer and use it in GitHub Desktop.
Sequelize patch to enable unique indices (including partial indices) for ON CONFLICT DO UPDATE with postgres or sqlite
diff --git a/node_modules/sequelize/lib/dialects/abstract/query-generator.js b/node_modules/sequelize/lib/dialects/abstract/query-generator.js
index d2c865d..b668f38 100755
--- a/node_modules/sequelize/lib/dialects/abstract/query-generator.js
+++ b/node_modules/sequelize/lib/dialects/abstract/query-generator.js
@@ -302,9 +302,16 @@ class QueryGenerator {
if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) {
if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite
// If no conflict target columns were specified, use the primary key names from options.upsertKeys
- const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));
const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`);
- onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`;
+ if (Array.isArray(options.upsertKeys)) {
+ const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));
+ onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`;
+ } else {
+ // If upsertKeys is an object, generate an ON CONFLICT WHERE
+ const conflictKeys = options.upsertKeys.fields.map(attr => this.quoteIdentifier(attr));
+ const where = this.whereQuery(options.upsertKeys.where);
+ onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) ${where} DO UPDATE SET ${updateKeys.join(',')}`;
+ }
} else { // mysql / maria
const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`);
onDuplicateKeyUpdate = `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`;
diff --git a/node_modules/sequelize/lib/model.js b/node_modules/sequelize/lib/model.js
index a4d957b..c9aa9ee 100644
--- a/node_modules/sequelize/lib/model.js
+++ b/node_modules/sequelize/lib/model.js
@@ -2704,10 +2704,22 @@ class Model {
// Map updateOnDuplicate attributes to fields
if (options.updateOnDuplicate) {
options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr);
- // Get primary keys for postgres to enable updateOnDuplicate
- options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value();
- if (Object.keys(model.uniqueKeys).length > 0) {
- options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map(c => c.fields[0]).value();
+ if (options.upsertIndex !== undefined) {
+ const upsertIndex = model._indexes.find(index => index.name === options.upsertIndex);
+ if (upsertIndex === undefined) {
+ throw new Error(`upsertIndex '${options.upsertIndex}' not defined.`);
+ }
+ if (!upsertIndex.unique) {
+ throw new Error(`upsertIndex '${options.upsertIndex}' not a unique index.`);
+ }
+ options.upsertKeys = { fields: upsertIndex.fields, where: upsertIndex.where };
+ }
+ if (options.upsertKeys == null) {
+ // Get primary keys for postgres to enable updateOnDuplicate
+ options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value();
+ if (Object.keys(model.uniqueKeys).length > 0) {
+ options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map(c => c.fields[0]).value();
+ }
}
}
diff --git a/node_modules/sequelize/types/lib/model.d.ts b/node_modules/sequelize/types/lib/model.d.ts
index 0f5ad3a..7d4b040 100644
--- a/node_modules/sequelize/types/lib/model.d.ts
+++ b/node_modules/sequelize/types/lib/model.d.ts
@@ -754,6 +754,20 @@ export interface BulkCreateOptions extends Logging, Transactionable {
*/
updateOnDuplicate?: string[];
+ /**
+ * The name of a unique index to be used for generation of an `ON CONFLICT`
+ * clause. (Only supported by SQLite >= 3.24.0 & Postgres >= 9.5)
+ */
+ upsertIndex?: string;
+
+ /**
+ * Either an array of database columns that are either primary keys or
+ * composite members of a unique key, or an object containing fields and a
+ * where clause that represents a partial index. Used for generating an `ON
+ * CONFLICT` clause. (Only supported by SQLite >= 3.24.0 & Postgres >= 9.5)
+ */
+ upsertKeys?: string[] | { fields: string[], where: WhereOptions };
+
/**
* Include options. See `find` for details
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment