onConflict().merge() without column args emits invalid SQL — columns===true guard missing in _merge()
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
_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
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 identifierSame issue in lib/dialects/sqlite3/query/sqlite-querycompiler.js line ~181.
Suggested fix
Mirror the guard from _ignore() in _merge():
_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.
Source: knex/knex