Report hasn't been filed before.
What version of drizzle-orm are you using?
0.45.2
What version of drizzle-kit are you using?
0.0.0
Other packages
No response
Describe the Bug
In 0.45.x, the $onUpdate callback is called unconditionally for every column that has one, even when that column's value is explicitly provided in the set object. In 0.44.6 the callback was lazy — it only fired when the column was absent from set.
This is a regression. Any $onUpdate callback with side effects (throwing, reading external state, logging) will now trigger on every UPDATE query, even those that explicitly supply the column value and would never use the hook's return value.
To Reproduce
const table = pgTable('example', {
id: uuid('id').primaryKey(),
name: text('name').notNull(),
updatedById: uuid('updated_by_id')
.$onUpdate(() => {
throw new Error('should not be called when column is explicitly set');
})
.notNull(),
});
// This should NOT invoke the $onUpdate callback — updatedById is explicitly provided.
await db.update(table).set({ name: 'foo', updatedById: 'some-uuid' }).where(...);
With 0.44.6 this completes without error. With 0.45.x it throws.
Expected behavior
The $onUpdate callback should only be invoked when the column is absent from set. Calling a callback whose return value will be unconditionally discarded is surprising and constitutes a breaking change for any callback with side effects — throwing, reading external state, logging, etc. The 0.44.6 behavior (lazy evaluation via ?? short-circuit) was consistent with this expectation.
Actual behavior
The callback is invoked for every column that has $onUpdate, regardless of whether that column appears in set. The return value is still correctly discarded via ??, but the side effect of calling the callback (e.g. a throw, reading external state, logging) always runs.
Root cause
In pg-core/dialect.ts, buildUpdateSet changed between versions:
// 0.44.6 — lazy: onUpdateFn only called when set[colName] is absent
const value = set[colName] ?? sql.param(col.onUpdateFn(), col);
// 0.45.x — eager: onUpdateFn always called, result conditionally used
const onUpdateFnResult = col.onUpdateFn?.();
const value = set[colName] ?? (is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col));
The variable was extracted (likely to support the new is(onUpdateFnResult, SQL) check) but the extraction moved the call outside the short-circuit.
Suggested fix
Keep the SQL-type check from 0.45.x but restore lazy evaluation:
// only invoke the hook when the column is absent from set
const onUpdateFnResult = set[colName] !== void 0 ? undefined : col.onUpdateFn?.();
const value = set[colName] ?? (is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col));
Environment
drizzle-orm: 0.45.x (regression from 0.44.6)
- Driver:
pg / postgres (PostgreSQL)
- Node: 24.x
Report hasn't been filed before.
What version of
drizzle-ormare you using?0.45.2
What version of
drizzle-kitare you using?0.0.0
Other packages
No response
Describe the Bug
In 0.45.x, the
$onUpdatecallback is called unconditionally for every column that has one, even when that column's value is explicitly provided in thesetobject. In 0.44.6 the callback was lazy — it only fired when the column was absent fromset.This is a regression. Any
$onUpdatecallback with side effects (throwing, reading external state, logging) will now trigger on everyUPDATEquery, even those that explicitly supply the column value and would never use the hook's return value.To Reproduce
With
0.44.6this completes without error. With0.45.xit throws.Expected behavior
The
$onUpdatecallback should only be invoked when the column is absent fromset. Calling a callback whose return value will be unconditionally discarded is surprising and constitutes a breaking change for any callback with side effects — throwing, reading external state, logging, etc. The 0.44.6 behavior (lazy evaluation via??short-circuit) was consistent with this expectation.Actual behavior
The callback is invoked for every column that has
$onUpdate, regardless of whether that column appears inset. The return value is still correctly discarded via??, but the side effect of calling the callback (e.g. athrow, reading external state, logging) always runs.Root cause
In
pg-core/dialect.ts,buildUpdateSetchanged between versions:The variable was extracted (likely to support the new
is(onUpdateFnResult, SQL)check) but the extraction moved the call outside the short-circuit.Suggested fix
Keep the
SQL-type check from 0.45.x but restore lazy evaluation:Environment
drizzle-orm: 0.45.x (regression from 0.44.6)pg/postgres(PostgreSQL)