Commit a4cf5342 authored by Dian Fay's avatar Dian Fay
Browse files

feat: onConflictUpdate option for true upserts in db.mytable.insert

parent b9e664be
Loading
Loading
Loading
Loading
+6 −0
Changes for lib/statement/insert.js: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ const Insert = function (source, record, options = {}) {
  this.generator = options.generator;
  this.stream = options.stream;
  this.onConflictIgnore = options.onConflictIgnore;
  this.onConflictUpdate = options.onConflictUpdate;
  this.deepInsert = Object.prototype.hasOwnProperty.call(options, 'deepInsert') && !!options.deepInsert;

  // get fields to return from options
@@ -88,6 +89,11 @@ Insert.prototype.format = function () {

  if (this.onConflictIgnore) {
    sql += 'ON CONFLICT DO NOTHING ';
  } else if (this.onConflictUpdate) {
    const conflictFields = this.onConflictUpdate.map(f => `"${f}"`);
    const excludedFields = _.difference(this.columns, this.onConflictUpdate).map(f => `"${f}" = EXCLUDED."${f}"`);

    sql += `ON CONFLICT (${conflictFields.join(', ')}) DO UPDATE SET ${excludedFields.join(', ')} `;
  }

  sql += `RETURNING ${this.fields.join(', ')}`;
+20 −0
Changes for test/statement/insert.js: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -99,6 +99,26 @@ describe('Insert', function () {
      assert.deepEqual(result.params, ['value1']);
    });

    describe('onConflictUpdate', function () {
      it('should handle onConflictUpdate option', function () {
        const result = new Insert(source, {field1: 'value1'}, {onConflictUpdate: ['id']});
        assert.equal(result.format(), 'INSERT INTO "testsource" ("field1") VALUES ($1) ON CONFLICT ("id") DO UPDATE SET "field1" = EXCLUDED."field1" RETURNING *');
        assert.deepEqual(result.params, ['value1']);
      });

      it('should handle onConflictUpdate option with multiple fields and conflict keys', function () {
        const result = new Insert(source, {
          field1: 'value1',
          object: 'value2'
        }, {
          onConflictUpdate: ['id', 'field2']
        });

        assert.equal(result.format(), 'INSERT INTO "testsource" ("field1", "object") VALUES ($1, $2) ON CONFLICT ("id", "field2") DO UPDATE SET "field1" = EXCLUDED."field1", "object" = EXCLUDED."object" RETURNING *');
        assert.deepEqual(result.params, ['value1', 'value2']);
      });
    });

    describe('deep insert', function () {
      it('should create junction queries', function () {
        const result = new Insert(
+20 −0
Changes for test/writable/insert.js: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -168,6 +168,26 @@ describe('insert', function () {
    });
  });

  it('upserts', function* () {
    const original = yield db.normal_pk.insert({field1: 'zeta'});
    const beforeCount = yield db.normal_pk.count();
    const conflict = yield db.normal_pk.insert({
      id: original.id,
      field1: 'eta'
    }, {
      onConflictUpdate: ['id']
    });

    const afterCount = yield db.normal_pk.count();

    assert.equal(beforeCount, afterCount);
    assert.equal(conflict.id, original.id);

    const final = yield db.normal_pk.findOne(original.id);

    assert.equal(final.field1, 'eta');
  });

  it('rejects if not insertable', function* () {
    let caught = false;