Skip to content

Implement supports_update_returning? to auto-reload virtual columns on UPDATE (Rails #48628) #2795

@yahonda

Description

@yahonda

Background

Rails #48628 (merged 2026-05-14) introduces an opt-in supports_update_returning? capability that lets adapters auto-reload virtual columns on update via a single UPDATE ... RETURNING ... round trip, avoiding the explicit record.reload that users need today.

The mechanism in Rails:

  • ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_update_returning? (default false).
  • ActiveRecord::ConnectionAdapters::DatabaseStatements#update_with_result(arel, name, binds, returning:) which sets arel.returning(...) (the Arel API added in rails/rails#47161).
  • ActiveRecord::ConnectionAdapters::Column#auto_populated_on_update? returning virtual?.
  • _update_row switches to _update_record_with_result when the adapter opts in and the model has any auto_populated_on_update? columns.

PostgreSQL aliases supports_update_returning? to supports_insert_returning?. MySQL and SQLite stay at false.

Why this applies to oracle-enhanced

Oracle natively supports UPDATE ... RETURNING ... INTO :bind, which is the same shape we already use for INSERT via OracleEnhanced::DatabaseStatements#sql_for_insert (lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb:283-304). The adapter currently leaves supports_update_returning? at the abstract default false, so Oracle users with virtual columns still need an explicit record.reload after update — the Rails default behaviour for adapters that haven't opted in.

There's no regression today: the spec suite passes against a Rails revision that includes PR #48628 (verified with bundle update activerecord on a topic branch). This issue tracks the opt-in.

Sketch of the work

Two viable approaches, mirroring the same translation problem flagged for direct-Arel .returning(...) callers in rails/rails#47161:

(a) Adapter-level SQL rewrite: introduce an update_with_result override (or a sql_for_update analog of sql_for_insert) that appends Oracle's RETURNING <cols> INTO :returning_<col> form, binds OUT parameters, and reads them back via the existing bind_returning_param / get_returning_param path used by _exec_insert in database_statements.rb. Keeps the Arel visitor untouched and reuses the INSERT plumbing.

(b) Arel visitor override: teach OracleCommon#visit_Arel_Nodes_UpdateStatement (and the matching Insert/Delete visitors) to translate o.returning into Oracle's RETURNING ... INTO :bind form. More uniform with how PostgreSQL handles this, but Arel doesn't model OUT binds, so the visitor still needs adapter cooperation.

(a) is closer to the existing pattern and the smaller change.

Acceptance

  • supports_update_returning? returns true.
  • Model#update of a column that feeds a virtual column refreshes the virtual column in-place without an extra SELECT (verify via capture_sql and a round-trip spec).
  • _update_record_with_result returns a result whose affected_rows matches the existing _update_record path for the same statement.
  • New specs in spec/active_record/connection_adapters/oracle_enhanced/ covering: virtual column reload on update, no RETURNING when there are no virtual columns, optimistic locking columns are still updated correctly, composite-PK paths remain intact.

Out of scope

  • STORED/MATERIALIZED column support — separately deferred (see existing notes about waiting for a public catalog flag that distinguishes MATERIALIZED from regular DEFAULT).
  • Direct-Arel .returning(...) support for hand-built Arel managers — separate question, same translation layer.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions