Skip to content

Replace slow UNION ALL query with optimized all_objects query#2521

Merged
yahonda merged 1 commit into
rsim:masterfrom
yahonda:add-describe-regression-test
Apr 21, 2026
Merged

Replace slow UNION ALL query with optimized all_objects query#2521
yahonda merged 1 commit into
rsim:masterfrom
yahonda:add-describe-regression-test

Conversation

@yahonda
Copy link
Copy Markdown
Collaborator

@yahonda yahonda commented Apr 13, 2026

Summary

  • Replace the 4-way UNION ALL query across all_tables, all_views, and all_synonyms inside OracleEnhanced::SchemaStatements#resolve_data_source_name with a single all_objects query for the common TABLE/VIEW lookup path.
  • Resolve synonym targets via a second all_synonyms query only when a synonym is found.
  • ORDER BY DECODE(...) preserves the legacy lookup priority (owner table, owner view, owner synonym, public synonym) in the single-pass query.
  • Add a regression spec covering table, view, materialized view, private synonym, and public synonym resolution for one underlying table.
  • Grant CREATE/DROP PUBLIC SYNONYM to test users so the new spec can run from a fresh setup.

Motivation

The original UNION ALL query causes ~2s delays in production (see #2429). This replaces it with a single all_objects query, which is significantly faster for the common TABLE/VIEW path. Based on the approach suggested in #2467.

Note on rebase

Rebased onto current master (past #2545 "Move describe to schema_statements as resolve_data_source_name"). The optimization now lives in OracleEnhanced::SchemaStatements#resolve_data_source_name rather than the former Connection#describe. Upstream's iterative loop and Set-based synonym cycle guard are preserved; the catalog lookup still goes through select_one(..., "SCHEMA", ...) so it participates in instrumentation and the query cache.

Test plan

  • Local bundle exec rspec .../connection_spec.rb — 77 examples, 0 failures, 1 pending (JDBC-only).
  • resolve_data_source_name block — 14 examples, 0 failures (includes the new combined regression).
  • bundle exec rubocop clean on both Ruby files.
  • Copilot CLI review (upstream/master..HEAD) — no actionable findings.
  • Verified materialized views are still found (backing table appears as TABLE in all_objects); combined regression spec now covers this case explicitly.

Co-Authored-By: N.lohitha lohithavarma36@gmail.com
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

@matthewtusker
Copy link
Copy Markdown

Can DBMS_UTILITY.NAME_RESOLVE be used here instead? We're using it in Production (via a Monkey Patch) and it's working perfectly.

yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 15, 2026
Per the suggestion in
rsim#2521 (comment),
replace the UNION ALL query against all_tables / all_views / all_synonyms
with a single DBMS_UTILITY.NAME_RESOLVE call. The package resolves private
and public synonyms internally, so the manual synonym recursion is removed.

DBMS_UTILITY.NAME_RESOLVE is documented for Oracle 8i (released 1999),
so version support is not a concern for any software being written in
2026. See the Oracle8i Supplied PL/SQL Packages Reference, Release 2
(8.1.6), A76936-01:
https://docs.oracle.com/cd/A87860_01/doc/appdev.817/a76936/dbms_uti.htm

OUT-bind plumbing is added on both the OCI8 path (anonymous PL/SQL block
with named binds) and the JDBC path (CallableStatement + registerOutParameter)
so the POC can be exercised on both MRI and JRuby.

Two subtleties the spec suite surfaced:

  1. NAME_RESOLVE's context parameter matters. context=1 is PL/SQL objects
     only and raises ORA-04047 for tables / views; context=0 covers
     tables, views, and synonyms (verified empirically against Oracle 23ai).

  2. NAME_RESOLVE uppercases unquoted input, so case-preserving (quoted)
     identifiers like "test_Mixed_Comments" failed with ORA-06564. Each
     dotted part of the name is wrapped in double quotes when the
     original identifier was not upcase-normalized by valid_table_name?.

With those in place the full rspec suite is green on CRuby 4.0.2 and
JRuby 10.0.5.0 (423 examples, 0 failures, 6 pending on both).

The regression test and PUBLIC SYNONYM grants from PR rsim#2521 are included
as the acceptance bar.

Open questions this POC exists to answer:
- Readability: anonymous PL/SQL block vs. SQL against all_* dictionaries.
- Consistency: the rest of the adapter reads from all_* dictionaries;
  a PL/SQL call is a style break.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 15, 2026
Adds script/benchmark_describe.rb comparing OracleEnhanced::Connection#describe
across three implementations on a 1000-object schema (700 tables + 100 views
+ 100 private synonyms + 100 public synonyms):
  - master: UNION ALL over all_tables / all_views / all_synonyms
  - PR rsim#2521: single all_objects query
  - this POC: DBMS_UTILITY.NAME_RESOLVE

Benchmark environment:
  - CPU: AMD Ryzen 9 7940HS (16 threads)
  - RAM: 60 GiB
  - OS: Ubuntu (kernel 7.0, x86_64)
  - Database: Oracle Database 23.26.1 Free, docker image oracle/database:23.26.1-free
    on localhost (no network hop)
  - Ruby: CRuby 4.0.2 (ruby-oci8 HEAD) / JRuby 10.0.5.0 (ojdbc17.jar)

The absolute numbers below are not meaningful on their own — they reflect
this one machine, one container, and one Oracle release. Only the
relative differences between the three implementations matter.

Shared fixtures across all six runs so every implementation hits the
same shared-pool / dictionary cache state.

Avg ms per describe() call (lower is better):

  case              master   PR rsim#2521   POC       master   PR rsim#2521   POC
                    CRuby    CRuby      CRuby     JRuby    JRuby      JRuby
  tables            0.744    0.311      0.104     1.501    0.723      0.308
  views             0.647    0.251      0.107     0.957    0.507      0.254
  private synonyms  1.440    1.157      0.234     2.184    1.908      0.235
  public synonyms   1.275    0.990      0.258     1.862    1.585      0.230
  all mixed         0.842    0.432      0.111     1.054    0.633      0.186

The "all mixed" row is the most representative of real Rails workloads:
the POC is ~7.6x faster than master and ~3.9x faster than PR rsim#2521 on
CRuby, and ~5.7x / ~3.4x on JRuby. The synonym win is the largest —
NAME_RESOLVE follows the synonym server-side in one round trip, whereas
PR rsim#2521 still issues a second all_synonyms query.

Also adds a before(:suite) hook that runs PURGE RECYCLEBIN. Dropped
tables from earlier spec runs accumulate in USER_RECYCLEBIN and can
mask issues (ORA-00955 on re-create, stale BIN\$... entries in
all_objects). The hook makes every rspec invocation start clean.

Fixture counts stay tunable via TABLE_COUNT / VIEW_COUNT / SYNONYM_COUNT.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yahonda
Copy link
Copy Markdown
Collaborator Author

yahonda commented Apr 15, 2026

Opened a PoC #2531

@yahonda yahonda mentioned this pull request Apr 16, 2026
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 20, 2026
Replaces the UNION ALL catalog query inside
SchemaStatements#resolve_data_source_name with a single
DBMS_UTILITY.NAME_RESOLVE PL/SQL call. Private and public synonyms
are chased server-side, so the iterative loop + visited Set used to
guard against circular synonyms is no longer needed -- Oracle
reports a cycle natively as ORA-00980 ("synonym translation is no
longer valid").

DBMS_UTILITY.NAME_RESOLVE is documented for Oracle 8i (released
1999), so version support is not a concern on any supported
release. See the Oracle8i Supplied PL/SQL Packages Reference,
Release 2 (8.1.6), A76936-01:
https://docs.oracle.com/cd/A87860_01/doc/appdev.817/a76936/dbms_uti.htm

Design notes

  1. Driver-layer helpers. OCIConnection#name_resolve uses an
     anonymous PL/SQL block with named binds. JDBCConnection#name_resolve
     uses CallableStatement + registerOutParameter. Both return
     [schema, object_name]; the caller is the adapter's
     SchemaStatements mix-in, not the raw driver.

  2. Instrumentation preserved. NAME_RESOLVE bypasses select_one
     entirely, so we wrap the call in an explicit sql.active_record
     SCHEMA notification. The regression spec that subscribes to
     that event and was introduced alongside the move-describe
     refactor stays green.

  3. Case handling. NAME_RESOLVE uppercases unquoted input, so
     mixed-case identifiers like "test_Mixed" fail with ORA-06564
     unless quoted. normalize_name_for_name_resolve upcases each
     dotted part when the part is a valid unquoted identifier and
     wraps it in double quotes otherwise; "sys.test_Mixed" becomes
     SYS."test_Mixed" rather than the all-quoted
     "sys"."test_Mixed" that would send Oracle hunting for a
     lowercase schema.

  4. context=0. NAME_RESOLVE's context argument matters:
     context=1 covers PL/SQL objects only and raises ORA-04047 for
     tables/views. context=0 covers tables, views, and synonyms.

Trade-off: the select_one-based resolve_data_source_name benefits
from the AR query cache on repeated describes in the same scope;
NAME_RESOLVE always hits the server. The win is round-trip count
(one call instead of UNION ALL + synonym loop), which dominates
on cold-cache / synonym-heavy schemas -- see benchmark results in
PR rsim#2531.

The benchmark script (script/benchmark_describe.rb) is retargeted
to adapter.send(:resolve_data_source_name, name) so all three
implementations (master UNION ALL, PR rsim#2521 all_objects, this POC)
can be measured against a shared 1000-object fixture. A
before(:suite) PURGE RECYCLEBIN hook keeps the suite from
accumulating stale BIN$... dictionary rows across rspec runs.
create_oracle_enhanced_users.sql adds CREATE/DROP PUBLIC SYNONYM
grants required by the benchmark's public-synonym fixtures.

Full rspec suite:
  - CRuby 4.0.2:  453 examples, 0 failures, 6 pending
  - JRuby 10.0.5.0: 458 examples, 0 failures, 6 pending

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yahonda yahonda force-pushed the add-describe-regression-test branch from 8ade577 to ae3e3d2 Compare April 21, 2026 00:01
…lve_data_source_name

Replace the 4-way UNION ALL across all_tables, all_views, and all_synonyms
inside SchemaStatements#resolve_data_source_name with a single all_objects
query for the common TABLE/VIEW path. Synonym targets are resolved via a
second all_synonyms query only when needed.

- ORDER BY DECODE preserves the legacy lookup priority within the single
  query: owner table, owner view, owner synonym, then public synonym.
- Adds a regression spec covering table, view, materialized view, private
  synonym, and public synonym resolution against one underlying table.
  Materialized views are covered implicitly via Oracle's backing TABLE
  row in all_objects, which matches the same object_type filter.
- Grants CREATE/DROP PUBLIC SYNONYM to the test users so the regression
  spec can run from a fresh setup.

Refs rsim#2429

Co-Authored-By: N.lohitha <lohithavarma36@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yahonda yahonda force-pushed the add-describe-regression-test branch from 808a051 to 0377453 Compare April 21, 2026 01:06
@yahonda
Copy link
Copy Markdown
Collaborator Author

yahonda commented Apr 21, 2026

Why this approach over the alternatives

Two alternatives were benchmarked head-to-head against this PR (23c and 11g, 900-object fixture, full numbers in #2566):

#2566 (DBMS_UTILITY.NAME_RESOLVE) — 2–3.5x faster than this PR on 23c, up to ~600x faster on the 11g synonym path. Rejected here because it replaces the SQL catalog query with a PL/SQL call (anonymous block on OCI / CallableStatement on JDBC), which is a style break from the adapter's uniform ALL_* dictionary access. The synonym-heavy advantage isn't representative of typical Rails on Oracle workloads. Parked as draft; revisit only if a concrete synonym-heavy workload is reported where the measured speedup clearly outweighs the added surface.

#2560 (USER_ fallback for 11g)* — adds a same_schema_as_user? helper plus three hand-picked ALL_*USER_* swaps, explicitly scheduled for removal when 11.2 support drops. This PR subsumes the resolve_data_source_name bucket (~86% of #2560's measured 11g wall-clock savings) without per-query branching. #2560's synonyms / foreign_keys wins (~50s on the 11g suite) would remain orthogonal, but the per-query dual code paths weren't judged worth carrying given most of the 11g pain is subsumed here. #2560 closed.

In short: this PR takes the middle path — large 11g win for the common table/view path, readable single-query SQL, and no per-version branching; the remaining synonym-heavy 11g headroom sits in #2566 as insurance if it's ever shown to matter in practice.

@yahonda yahonda merged commit 5c9feae into rsim:master Apr 21, 2026
11 checks passed
@yahonda yahonda deleted the add-describe-regression-test branch April 21, 2026 01:21
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 24, 2026
Replace slow UNION ALL query with optimized all_objects query
yahonda added a commit that referenced this pull request Apr 24, 2026
Backport #2545 and #2521 to release81: move Connection#describe helpers and optimize catalog lookup
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 29, 2026
Replaces the UNION ALL catalog query inside
SchemaStatements#resolve_data_source_name with a single
DBMS_UTILITY.NAME_RESOLVE PL/SQL call. Private and public synonyms
are chased server-side, so the iterative loop + visited Set used to
guard against circular synonyms is no longer needed -- Oracle
reports a cycle natively as ORA-00980 ("synonym translation is no
longer valid").

DBMS_UTILITY.NAME_RESOLVE is documented for Oracle 8i (released
1999), so version support is not a concern on any supported
release. See the Oracle8i Supplied PL/SQL Packages Reference,
Release 2 (8.1.6), A76936-01:
https://docs.oracle.com/cd/A87860_01/doc/appdev.817/a76936/dbms_uti.htm

Design notes

  1. Driver-layer helpers. OCIConnection#name_resolve uses an
     anonymous PL/SQL block with named binds. JDBCConnection#name_resolve
     uses CallableStatement + registerOutParameter. Both return
     [schema, object_name]; the caller is the adapter's
     SchemaStatements mix-in, not the raw driver.

  2. Instrumentation preserved. NAME_RESOLVE bypasses select_one
     entirely, so we wrap the call in an explicit sql.active_record
     SCHEMA notification. The regression spec that subscribes to
     that event and was introduced alongside the move-describe
     refactor stays green.

  3. Case handling. NAME_RESOLVE uppercases unquoted input, so
     mixed-case identifiers like "test_Mixed" fail with ORA-06564
     unless quoted. normalize_name_for_name_resolve upcases each
     dotted part when the part is a valid unquoted identifier and
     wraps it in double quotes otherwise; "sys.test_Mixed" becomes
     SYS."test_Mixed" rather than the all-quoted
     "sys"."test_Mixed" that would send Oracle hunting for a
     lowercase schema.

  4. context=0. NAME_RESOLVE's context argument matters:
     context=1 covers PL/SQL objects only and raises ORA-04047 for
     tables/views. context=0 covers tables, views, and synonyms.

Trade-off: the select_one-based resolve_data_source_name benefits
from the AR query cache on repeated describes in the same scope;
NAME_RESOLVE always hits the server. The win is round-trip count
(one call instead of UNION ALL + synonym loop), which dominates
on cold-cache / synonym-heavy schemas -- see benchmark results in
PR rsim#2531.

The benchmark script (script/benchmark_describe.rb) is retargeted
to adapter.send(:resolve_data_source_name, name) so all three
implementations (master UNION ALL, PR rsim#2521 all_objects, this POC)
can be measured against a shared 1000-object fixture. A
before(:suite) PURGE RECYCLEBIN hook keeps the suite from
accumulating stale BIN$... dictionary rows across rspec runs.
create_oracle_enhanced_users.sql adds CREATE/DROP PUBLIC SYNONYM
grants required by the benchmark's public-synonym fixtures.

Full rspec suite:
  - CRuby 4.0.2:  453 examples, 0 failures, 6 pending
  - JRuby 10.0.5.0: 458 examples, 0 failures, 6 pending

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Apr 29, 2026
Replaces the UNION ALL catalog query inside
SchemaStatements#resolve_data_source_name with a single
DBMS_UTILITY.NAME_RESOLVE PL/SQL call. Private and public synonyms
are chased server-side, so the iterative loop + visited Set used to
guard against circular synonyms is no longer needed -- Oracle
reports a cycle natively as ORA-00980 ("synonym translation is no
longer valid").

DBMS_UTILITY.NAME_RESOLVE is documented for Oracle 8i (released
1999), so version support is not a concern on any supported
release. See the Oracle8i Supplied PL/SQL Packages Reference,
Release 2 (8.1.6), A76936-01:
https://docs.oracle.com/cd/A87860_01/doc/appdev.817/a76936/dbms_uti.htm

Design notes

  1. Driver-layer helpers. OCIConnection#name_resolve uses an
     anonymous PL/SQL block with named binds. JDBCConnection#name_resolve
     uses CallableStatement + registerOutParameter. Both return
     [schema, object_name]; the caller is the adapter's
     SchemaStatements mix-in, not the raw driver.

  2. Instrumentation preserved. NAME_RESOLVE bypasses select_one
     entirely, so we wrap the call in an explicit sql.active_record
     SCHEMA notification. The regression spec that subscribes to
     that event and was introduced alongside the move-describe
     refactor stays green.

  3. Case handling. NAME_RESOLVE uppercases unquoted input, so
     mixed-case identifiers like "test_Mixed" fail with ORA-06564
     unless quoted. normalize_name_for_name_resolve upcases each
     dotted part when the part is a valid unquoted identifier and
     wraps it in double quotes otherwise; "sys.test_Mixed" becomes
     SYS."test_Mixed" rather than the all-quoted
     "sys"."test_Mixed" that would send Oracle hunting for a
     lowercase schema.

  4. context=0. NAME_RESOLVE's context argument matters:
     context=1 covers PL/SQL objects only and raises ORA-04047 for
     tables/views. context=0 covers tables, views, and synonyms.

Trade-off: the select_one-based resolve_data_source_name benefits
from the AR query cache on repeated describes in the same scope;
NAME_RESOLVE always hits the server. The win is round-trip count
(one call instead of UNION ALL + synonym loop), which dominates
on cold-cache / synonym-heavy schemas -- see benchmark results in
PR rsim#2531.

The benchmark script (script/benchmark_describe.rb) is retargeted
to adapter.send(:resolve_data_source_name, name) so all three
implementations (master UNION ALL, PR rsim#2521 all_objects, this POC)
can be measured against a shared 1000-object fixture. A
before(:suite) PURGE RECYCLEBIN hook keeps the suite from
accumulating stale BIN$... dictionary rows across rspec runs.
create_oracle_enhanced_users.sql adds CREATE/DROP PUBLIC SYNONYM
grants required by the benchmark's public-synonym fixtures.

Full rspec suite:
  - CRuby 4.0.2:  453 examples, 0 failures, 6 pending
  - JRuby 10.0.5.0: 458 examples, 0 failures, 6 pending

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 3, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 3, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 3, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 3, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 3, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
all_objects query + secondary all_synonyms lookup since rsim#2521) with a
`DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private and
public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Implementation:
- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`).
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed"`). Previously `%("#{part}")` would
  re-quote an already-quoted part, producing `""test_Mixed""` and
  raising ORA-01741.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.

Benchmark script (`script/benchmark_describe.rb`) carried over from
the POC reference at rsim#2566.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
`all_objects` query + secondary `all_synonyms` lookup since rsim#2521)
with a `DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private
and public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Numbers measured against an out-of-tree benchmark script (kept on the
POC branch rsim#2566); the gem itself does not ship a benchmark file.

Implementation:

- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`). Both wrap the call in `with_retry` so a
  stale connection is marked dead consistently with the rest of the
  adapter.
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed_Case_Desc"`) and doubles embedded
  `"` characters in unquoted parts (Oracle's `""` escape). Without
  this, `%("#{part}")` would re-quote an already-quoted part,
  producing `""test_Mixed""` and raising ORA-01741.
- `split_dotted_name` is a quote-aware splitter so `"Foo.Bar"`
  (quoted dot) survives intact, including the `""` escape sequence
  inside a quoted identifier.
- Malformed dotted names with empty parts (`schema..table`) are
  rejected with `ArgumentError` before reaching NAME_RESOLVE.
- DB-link names (`name@db_link`) are rejected up-front with
  `ArgumentError` — `data_source_exists?` is not the right entry
  point for resolving remote objects.
- Circular synonyms surface as ORA-00980 from NAME_RESOLVE, wrapped
  as `OracleEnhanced::ConnectionException`. No Ruby-side stack
  overflow, clean error.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.
- The new path runs through an anonymous PL/SQL block (OCI) /
  `CallableStatement` (JDBC), bypassing `select_one`. The
  `sql.active_record` SCHEMA notification is preserved via explicit
  `instrumenter.instrument`, so logging and other subscribers keep
  seeing the describe call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request May 4, 2026
Replaces the catalog-based `resolve_data_source_name` (single
`all_objects` query + secondary `all_synonyms` lookup since rsim#2521)
with a `DBMS_UTILITY.NAME_RESOLVE` call. NAME_RESOLVE chases private
and public synonyms inside the PL/SQL engine and returns
`[owner, object_name]` in one round trip, so the work does not depend
on `ALL_*` dictionary-view cost — the failure mode that rsim#2521's
secondary `all_synonyms` lookup leaves behind on synonym-heavy
schemas.

Now that PR rsim#2513 has landed `DBMS_METADATA.GET_DDL` in the adapter,
using another `DBMS_*` package is no longer a style break.

Benchmark on Oracle 23ai with a 1000-object fixture (700 tables +
100 views + 100 private synonyms + 100 public synonyms):

| case             | master   | this PR  | speedup |
|------------------|---------:|---------:|--------:|
| tables           |  0.384ms |  0.300ms |   1.3x  |
| views            |  0.364ms |  0.303ms |   1.2x  |
| private synonyms | 13.031ms |  0.447ms |    29x  |
| public synonyms  |  1.723ms |  0.454ms |   3.8x  |
| all mixed        |  0.578ms |  0.265ms |   2.2x  |

Numbers measured against an out-of-tree benchmark script (kept on the
POC branch rsim#2566); the gem itself does not ship a benchmark file.

Implementation:

- OUT-bind plumbing on both the OCI8 path (anonymous PL/SQL block
  with named binds) and the JDBC path (`CallableStatement` +
  `registerOutParameter`). Both wrap the call in `with_retry` so a
  stale connection is marked dead consistently with the rest of the
  adapter.
- `normalize_name_for_name_resolve` preserves case for already-quoted
  identifiers (e.g. `"test_Mixed_Case_Desc"`) and doubles embedded
  `"` characters in unquoted parts (Oracle's `""` escape). Without
  this, `%("#{part}")` would re-quote an already-quoted part,
  producing `""test_Mixed""` and raising ORA-01741.
- `split_dotted_name` is a quote-aware splitter so `"Foo.Bar"`
  (quoted dot) survives intact, including the `""` escape sequence
  inside a quoted identifier.
- Malformed identifiers raise `ArgumentError` up-front instead of
  being silently mangled and rejected by Oracle as an unhelpful
  `ConnectionException`. Three shapes are caught:
  * empty parts (`schema..table`, `.foo`, `foo.`)
  * three or more parts (`schema."table".extra` — NAME_RESOLVE with
    `context = 0` only accepts `schema.object`)
  * half-quoted parts (`"foo`, `foo"` — opens a quoted identifier
    that never closes, or vice versa). Embedded `"` mid-part
    (`Test"x`) is still allowed; it goes through the quote-and-
    double-escape branch.
- DB-link names (`name@db_link`) are rejected up-front with
  `ArgumentError` — `data_source_exists?` is not the right entry
  point for resolving remote objects.
- Circular synonyms surface as ORA-00980 from NAME_RESOLVE, wrapped
  as `OracleEnhanced::ConnectionException`. No Ruby-side stack
  overflow, clean error.
- `resolve_data_source_name` keeps master's contract: raise
  `OracleEnhanced::ConnectionException` for a non-existent name so
  `data_source_exists?`'s `rescue` returns false.
- The new path runs through an anonymous PL/SQL block (OCI) /
  `CallableStatement` (JDBC), bypassing `select_one`. The
  `sql.active_record` SCHEMA notification is preserved via explicit
  `instrumenter.instrument`, so logging and other subscribers keep
  seeing the describe call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

2 participants