Replace slow UNION ALL query with optimized all_objects query#2521
Conversation
3f590fd to
8ade577
Compare
|
Can |
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>
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>
|
Opened a PoC #2531 |
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>
8ade577 to
ae3e3d2
Compare
…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>
808a051 to
0377453
Compare
Why this approach over the alternativesTwo 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 / #2560 (USER_ fallback for 11g)* — adds a 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. |
Replace slow UNION ALL query with optimized all_objects query
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Summary
UNION ALLquery acrossall_tables,all_views, andall_synonymsinsideOracleEnhanced::SchemaStatements#resolve_data_source_namewith a singleall_objectsquery for the common TABLE/VIEW lookup path.all_synonymsquery 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.CREATE/DROP PUBLIC SYNONYMto test users so the new spec can run from a fresh setup.Motivation
The original
UNION ALLquery causes ~2s delays in production (see #2429). This replaces it with a singleall_objectsquery, 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 inOracleEnhanced::SchemaStatements#resolve_data_source_namerather than the formerConnection#describe. Upstream's iterative loop andSet-based synonym cycle guard are preserved; the catalog lookup still goes throughselect_one(..., "SCHEMA", ...)so it participates in instrumentation and the query cache.Test plan
bundle exec rspec .../connection_spec.rb— 77 examples, 0 failures, 1 pending (JDBC-only).resolve_data_source_nameblock — 14 examples, 0 failures (includes the new combined regression).bundle exec rubocopclean on both Ruby files.TABLEinall_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