Summary
DbMap.UpdateColumns can reuse an update plan generated by an earlier Update
or UpdateColumns call with a different ColumnFilter. The filter is part of
the caller's intended write set, so a filtered update can still generate SQL and
bind values for columns rejected by the current filter.
Affected area
Tested on main at c0288adf3b533244dd2bf2fca08ca4418b25d281.
Affected paths:
DbMap.Update
DbMap.UpdateColumns
TableMap.bindUpdate
Reproduction
A focused regression uses a database/sql capture driver. First call Update
for a row with PublicNote, Admin, and Balance. Then call UpdateColumns
with a filter that only accepts PublicNote.
Expected second query:
update "security_rows" set "PublicNote"=? where "ID"=?;
Actual second query:
update "security_rows" set "PublicNote"=?, "Admin"=?, "Balance"=? where "ID"=?;
The second call also binds values for Admin and Balance.
Impact
Consumer code that treats UpdateColumns as an allowed-column boundary can
unintentionally write protected fields after another path has primed the
table's cached update plan.
Root cause
TableMap.bindUpdate caches one updatePlan per table through plan.once.Do.
The cached plan is keyed by the table, not by the supplied ColumnFilter, so
the first call's filter controls later calls.
Suggested fix
Keep cached update plans for unfiltered updates, but build filtered update plans
per call, or key filtered plans by an explicit stable filter identity.
Add regression coverage for full-to-filtered and filtered-to-filtered update
sequences.
Summary
DbMap.UpdateColumnscan reuse an update plan generated by an earlierUpdateor
UpdateColumnscall with a differentColumnFilter. The filter is part ofthe caller's intended write set, so a filtered update can still generate SQL and
bind values for columns rejected by the current filter.
Affected area
Tested on
mainatc0288adf3b533244dd2bf2fca08ca4418b25d281.Affected paths:
DbMap.UpdateDbMap.UpdateColumnsTableMap.bindUpdateReproduction
A focused regression uses a
database/sqlcapture driver. First callUpdatefor a row with
PublicNote,Admin, andBalance. Then callUpdateColumnswith a filter that only accepts
PublicNote.Expected second query:
update "security_rows" set "PublicNote"=? where "ID"=?;Actual second query:
update "security_rows" set "PublicNote"=?, "Admin"=?, "Balance"=? where "ID"=?;The second call also binds values for
AdminandBalance.Impact
Consumer code that treats
UpdateColumnsas an allowed-column boundary canunintentionally write protected fields after another path has primed the
table's cached update plan.
Root cause
TableMap.bindUpdatecaches oneupdatePlanper table throughplan.once.Do.The cached plan is keyed by the table, not by the supplied
ColumnFilter, sothe first call's filter controls later calls.
Suggested fix
Keep cached update plans for unfiltered updates, but build filtered update plans
per call, or key filtered plans by an explicit stable filter identity.
Add regression coverage for full-to-filtered and filtered-to-filtered update
sequences.