#6504·knex

onConflict().merge() without column args emits invalid SQL — columns===true guard missing in _merge()

Author: tsushanthCreated Jul 22, 2026Updated Jul 22, 2026

Bug

Calling .onConflict().merge() (no column argument) generates invalid SQL in the PostgreSQL and SQLite3 dialects because _merge() lacks the columns === true guard that _ignore() already has.

Root cause

onConflict() called with no arguments sets the internal columns to true (the sentinel for "no target"). Both _ignore() and _merge() receive this value, but only _ignore() guards for it:

lib/dialects/postgres/query/pg-querycompiler.js line 154–157

javascript
_ignore(columns) {
  if (columns === true) {          // ← handles the no-arg case
    return ' on conflict do nothing';
  }
  ...
}

_merge(updates, columns, insert) {  // ← no guard
  let sql = ` on conflict ${this._onConflictClause(columns)} do update set `;
  ...
}

_onConflictClause(true)columnize(true)wrap(true)"true" (a quoted identifier).

Reproduction

javascript
import knex from 'knex';
const pg = knex({ client: 'pg' });

pg('users').insert({ email: '[email protected]' }).onConflict().merge().toSQL().sql
// → insert into "users" ("email") values (?) on conflict ("true") do update set "email" = excluded."email"
//                                                       ^^^^^^ should be an error, not a quoted identifier

Same issue in lib/dialects/sqlite3/query/sqlite-querycompiler.js line ~181.

Suggested fix

Mirror the guard from _ignore() in _merge():

javascript
_merge(updates, columns, insert) {
  if (columns === true) {
    throw new Error(
      '.onConflict().merge() requires a conflict target — pass column name(s) to .onConflict(columns)'
    );
  }
  let sql = ` on conflict ${this._onConflictClause(columns)} do update set `;
  ...
}

PostgreSQL does not allow DO UPDATE SET without a conflict target, so throwing is correct. The SQLite3 dialect can apply the same fix.