Skip to content

Mark the order_hacks-injected ORDER BY SqlLiteral as retryable#2770

Merged
yahonda merged 1 commit into
rsim:masterfrom
yahonda:order-hacks-retryable
May 12, 2026
Merged

Mark the order_hacks-injected ORDER BY SqlLiteral as retryable#2770
yahonda merged 1 commit into
rsim:masterfrom
yahonda:order-hacks-retryable

Conversation

@yahonda

@yahonda yahonda commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Companion to #2766. Arel::Visitors::OracleCommon#order_hacks rewrites the outer ORDER BY of DISTINCT ... FIRST_VALUE(...) queries by pushing Nodes::SqlLiteral.new("alias_N__ DESC NULLS ...") into o.orders. SqlLiteral defaults to retryable: false, and visit_Arel_Nodes_SqlLiteral runs collector.retryable &&= o.retryable, so this single literal flips the whole SELECT's collector.retryable to false.

Both Arel::Visitors::Oracle (pre-12c) and Arel::Visitors::Oracle12 route through the same order_hacks helper, so DISTINCT-with-outside-order queries on either visitor are affected. AR core's with_raw_connection(allow_retry: true) reads collector.retryable back into allow_retry, so the practical fallout is that those SELECTs miss the AR-level reconnect-and-retry layer even though they are plain idempotent SELECTs.

Pass retryable: true so the rewrite is neutral on retryability. This matches the upstream Rails convention for visitor-injected SqlLiteral: Arel::Visitors::MySQL line 30 does o.froms ||= Arel.sql("DUAL", retryable: true) and line 106 does the same for projection rewrites.

Adds a regression spec in arel/oracle_spec.rb that builds the DISTINCT+FIRST_VALUE shape, runs it through the visitor with a collector preset to retryable = true (matching AR's to_sql_and_binds preamble), and asserts the collector still reports retryable true after compilation.

Test plan

  • BUNDLE_ONLY=rubocop bundle exec rubocop — clean (95 files, 0 offenses)
  • CI test / test_11garel/oracle_spec.rb exercises the visitor in both versions via OracleCommon

`Arel::Visitors::OracleCommon#order_hacks` rewrites the outer ORDER
BY of `DISTINCT ... FIRST_VALUE(...)` queries (Oracle's
`columns_for_distinct` rewrite for ordering on columns outside the
SELECT list) by pushing `Nodes::SqlLiteral.new("alias_N__ DESC NULLS
...")` back into `o.orders`. `SqlLiteral` defaults to
`retryable: false`, and `visit_Arel_Nodes_SqlLiteral` runs
`collector.retryable &&= o.retryable`, so this single literal flips
the whole SELECT's `collector.retryable` to false even though the
query is a plain idempotent SELECT.

The bug is the same shape as the ROWNUM `SqlLiteral` issue fixed in
rsim#2766 — a visitor-internal `SqlLiteral` defeating AR's
`with_raw_connection(allow_retry: true)`. Both `Arel::Visitors::Oracle`
(pre-12c) and `Arel::Visitors::Oracle12` route through the same
`order_hacks` helper, so DISTINCT queries that hit this branch on
either visitor are affected.

Pass `retryable: true` so the rewrite is neutral on retryability,
matching the upstream Rails convention for visitor-injected
`SqlLiteral` (see `Arel::Visitors::MySQL` line 30/106:
`o.froms ||= Arel.sql("DUAL", retryable: true)` and the
`Arel.sql(quote_column_name(...), retryable: true)` projection
rewrite).

Add a regression spec in `arel/oracle_spec.rb` that builds the
DISTINCT+FIRST_VALUE shape, runs it through the visitor with a
collector preset to `retryable = true` (matching AR's
`to_sql_and_binds` preamble), and asserts the collector still
reports `retryable` true after compilation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yahonda yahonda merged commit 5e11f0f into rsim:master May 12, 2026
12 checks passed
@yahonda yahonda deleted the order-hacks-retryable branch May 12, 2026 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant