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
Background
Rails #48628 (merged 2026-05-14) introduces an opt-in
supports_update_returning?capability that lets adapters auto-reload virtual columns onupdatevia a singleUPDATE ... RETURNING ...round trip, avoiding the explicitrecord.reloadthat users need today.The mechanism in Rails:
ActiveRecord::ConnectionAdapters::AbstractAdapter#supports_update_returning?(defaultfalse).ActiveRecord::ConnectionAdapters::DatabaseStatements#update_with_result(arel, name, binds, returning:)which setsarel.returning(...)(the Arel API added in rails/rails#47161).ActiveRecord::ConnectionAdapters::Column#auto_populated_on_update?returningvirtual?._update_rowswitches to_update_record_with_resultwhen the adapter opts in and the model has anyauto_populated_on_update?columns.PostgreSQL aliases
supports_update_returning?tosupports_insert_returning?. MySQL and SQLite stay atfalse.Why this applies to oracle-enhanced
Oracle natively supports
UPDATE ... RETURNING ... INTO :bind, which is the same shape we already use forINSERTviaOracleEnhanced::DatabaseStatements#sql_for_insert(lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb:283-304). The adapter currently leavessupports_update_returning?at the abstract defaultfalse, so Oracle users with virtual columns still need an explicitrecord.reloadafterupdate— 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 activerecordon 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_resultoverride (or asql_for_updateanalog ofsql_for_insert) that appends Oracle'sRETURNING <cols> INTO :returning_<col>form, binds OUT parameters, and reads them back via the existingbind_returning_param/get_returning_parampath used by_exec_insertindatabase_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 translateo.returninginto Oracle'sRETURNING ... INTO :bindform. 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?returnstrue.Model#updateof a column that feeds a virtual column refreshes the virtual column in-place without an extraSELECT(verify viacapture_sqland a round-trip spec)._update_record_with_resultreturns a result whoseaffected_rowsmatches the existing_update_recordpath for the same statement.spec/active_record/connection_adapters/oracle_enhanced/covering: virtual column reload onupdate, no RETURNING when there are no virtual columns, optimistic locking columns are still updated correctly, composite-PK paths remain intact.Out of scope
.returning(...)support for hand-built Arel managers — separate question, same translation layer.References
RETURNINGtoINSERT/UPDATE/DELETEstatements rails/rails#47161 (Arel-level.returningAPI consumed byupdate_with_result)lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb:283-304(sql_for_insert, the INSERT-side analog to model on)lib/arel/visitors/oracle_common.rb:106-113(currentvisit_Arel_Nodes_UpdateStatement, which falls through tosuperand would surface the new RETURNING emission unchanged)