Use DBMS_METADATA for structure dump#2513
Merged
yahonda merged 1 commit intoMay 3, 2026
Merged
Conversation
Collaborator
Author
|
Heads-up: #2615 (currently open) restores trigger-based primary key generation as the
The Ruby-flavored |
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 2, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL`, the Oracle-native equivalent of `pg_dump --schema-only` / `mysqldump --no-data`. The original `ALL_*` data-dictionary-based implementation in `StructureDump` is left untouched. A new module `StructureDump::DbmsMetadata` prepends to `StructureDump` and dispatches each entry point — `structure_dump`, `structure_dump_db_stored_code`, `structure_dump_synonyms` — based on `OracleEnhancedAdapter.structure_dump_method`: * `:dbms_metadata` (default) — call the new DBMS_METADATA path. * `:data_dictionary` — fall through via `super` to the existing `ALL_*`-based implementation. The toggle is intentionally a Rails-app-global `cattr_accessor` rather than a per-connection `database.yml` key. The choice of structure-dump backend is an implementation strategy, not something that varies across the databases an app connects to. The `:data_dictionary` path is retained as a fallback while the new backend stabilises and may be deprecated and eventually removed in a future release. Defaulting to `:dbms_metadata` ensures real-world usage (and bug reports against the new path) before the old one is dropped — the original `ALL_*` implementation has needed regular touch-ups for schema features (most recently CHECK constraints in rsim#2500) that `DBMS_METADATA` would surface for free. Output transforms are configured to match the spirit of mysqldump / pg_dump --schema-only: * STORAGE / TABLESPACE / SEGMENT_ATTRIBUTES → FALSE (no installation-specific noise) * EMIT_SCHEMA → FALSE (portable across schemas) * SQLTERMINATOR → FALSE (Rails uses STATEMENT_TOKEN to split DDL on load) * CONSTRAINTS → TRUE, REF_CONSTRAINTS → FALSE (inline column constraints with the table; emit referential constraints separately as ALTER TABLE statements after all tables are created so DDL load order is correct) * PRETTY → TRUE Triggers emitted by the `primary_key_trigger:` opt-in (rsim#2615) are picked up via `GET_DEPENDENT_DDL('TRIGGER', table_name)` so a structure dump + load round-trip recreates the table + sequence + trigger faithfully. Tests: * `structure_dump_spec.rb` — existing examples assume the data-dictionary backend's exact DDL text; pinned to `structure_dump_method = :data_dictionary` in `before(:all)` so they keep verifying that path. * `dbms_metadata_structure_dump_spec.rb` — new file covering the DBMS_METADATA path. Asserts on the dump's structural shape (presence of CREATE TABLE / CREATE INDEX / ALTER TABLE … REFERENCES / COMMENT ON statements; absence of STORAGE / TABLESPACE clauses) rather than exact DDL text. Also covers the toggle: default value, delegation to `:data_dictionary`, and `ArgumentError` on unknown values. Test plan: * `bundle exec rspec` on Oracle 23ai — 711 examples, 0 failures, 11 pending. * `bundle exec rubocop` — clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
e3ee0a7 to
3157801
Compare
3 tasks
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
`structure_dump` (the `:data_dictionary` backend) emits identifiers inconsistently: tables and columns inside `CREATE TABLE` / `CREATE INDEX` are quoted, but PRIMARY KEY column lists, UNIQUE-constraint `ALTER TABLE` statements, view names, and synonym names are not. The result is gratuitous diff noise across runs and unnecessary divergence from the `:dbms_metadata` backend (rsim#2513), which always quotes. Quote table names, column names, constraint names, view names, and synonym names consistently in: - structure_dump_primary_key - structure_dump_unique_keys - structure_dump_views - structure_dump_synonyms Also drop the stray double space in `CREATE INDEX`. The space is emitted explicitly by the template (`CREATE#{' UNIQUE' if unique} INDEX ...`) so a future edit cannot silently produce `CREATE UNIQUEINDEX`. Compact the PK / unique-key column lists before quoting so a sparse `position` array cannot reach `quote_column_name(nil)`. 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
`structure_dump` emits identifiers inconsistently: tables and columns
inside `CREATE TABLE` / `CREATE INDEX` are quoted, but PRIMARY KEY
column lists, UNIQUE-constraint `ALTER TABLE` statements, view names,
and synonym names are not. The result is gratuitous diff noise across
runs.
Quote table names, column names, constraint names, view names, and
synonym names consistently in:
- structure_dump_primary_key
- structure_dump_unique_keys
- structure_dump_views
- structure_dump_synonyms
Also drop the stray double space in `CREATE INDEX`. The space is
emitted explicitly by the template
(`CREATE#{' UNIQUE' if unique} INDEX ...`) so a future edit cannot
silently produce `CREATE UNIQUEINDEX`. Compact the PK / unique-key
column lists before quoting so a sparse `position` array cannot
reach `quote_column_name(nil)`.
The unquoted identifiers were surfaced while exploring a
DBMS_METADATA-based structure_dump backend in rsim#2513; this change
stands independent of that work.
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
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL`, the Oracle-native equivalent of `pg_dump --schema-only` / `mysqldump --no-data`. The original `ALL_*` data-dictionary-based implementation in `StructureDump` is left untouched. A new module `StructureDump::DbmsMetadata` prepends to `StructureDump` and dispatches each entry point — `structure_dump`, `structure_dump_db_stored_code`, `structure_dump_synonyms` — based on `OracleEnhancedAdapter.structure_dump_method`: * `:dbms_metadata` (default) — call the new DBMS_METADATA path. * `:data_dictionary` — fall through via `super` to the existing `ALL_*`-based implementation. The toggle is intentionally a Rails-app-global `cattr_accessor` rather than a per-connection `database.yml` key. The choice of structure-dump backend is an implementation strategy, not something that varies across the databases an app connects to. The `:data_dictionary` path is retained as a fallback while the new backend stabilises and may be deprecated and eventually removed in a future release. Defaulting to `:dbms_metadata` ensures real-world usage (and bug reports against the new path) before the old one is dropped — the original `ALL_*` implementation has needed regular touch-ups for schema features (most recently CHECK constraints in rsim#2500) that `DBMS_METADATA` would surface for free. Output transforms are configured to match the spirit of mysqldump / pg_dump --schema-only: * STORAGE / TABLESPACE / SEGMENT_ATTRIBUTES → FALSE (no installation-specific noise) * EMIT_SCHEMA → FALSE (portable across schemas) * SQLTERMINATOR → FALSE (Rails uses STATEMENT_TOKEN to split DDL on load) * CONSTRAINTS → TRUE, REF_CONSTRAINTS → FALSE (inline column constraints with the table; emit referential constraints separately as ALTER TABLE statements after all tables are created so DDL load order is correct) * PRETTY → TRUE Triggers emitted by the `primary_key_trigger:` opt-in (rsim#2615) are picked up via `GET_DEPENDENT_DDL('TRIGGER', table_name)` so a structure dump + load round-trip recreates the table + sequence + trigger faithfully. Tests: * `structure_dump_spec.rb` — existing examples assume the data-dictionary backend's exact DDL text; pinned to `structure_dump_method = :data_dictionary` in `before(:all)` so they keep verifying that path. * `dbms_metadata_structure_dump_spec.rb` — new file covering the DBMS_METADATA path. Asserts on the dump's structural shape (presence of CREATE TABLE / CREATE INDEX / ALTER TABLE … REFERENCES / COMMENT ON statements; absence of STORAGE / TABLESPACE clauses) rather than exact DDL text. Also covers the toggle: default value, delegation to `:data_dictionary`, and `ArgumentError` on unknown values. Test plan: * `bundle exec rspec` on Oracle 23ai — 711 examples, 0 failures, 11 pending. * `bundle exec rubocop` — clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
c3842d7 to
71baf24
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`, not a strict database capability check. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via `GET_DEPENDENT_DDL("TRIGGER", table_name)`. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects `'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TRIGGER', 'TYPE'` only — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting `'PACKAGE BODY'` would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape (`CREATE TABLE` / `CREATE INDEX` / `ALTER TABLE … ADD CONSTRAINT` / `COMMENT` / no `STORAGE` / `TABLESPACE` / `PCTFREE`) rather than exact byte-level DDL. The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
e9bc077 to
77fec0e
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
77fec0e to
8a5467d
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
e2c379f to
cb4af6d
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cb4af6d to
b725280
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
b725280 to
890c4ed
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
890c4ed to
e0aaa5c
Compare
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 3, 2026
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
e0aaa5c to
be13c01
Compare
Closes rsim#2513. Adds a second `structure_dump` implementation built on Oracle's `DBMS_METADATA.GET_DDL` / `GET_DEPENDENT_DDL` alongside the existing data-dictionary (`ALL_*`) one. The new backend lives in a separate file and installs via `prepend DbmsMetadata`, so the legacy path in `structure_dump.rb` is untouched. Selectable globally via `OracleEnhancedAdapter.structure_dump_method`: - `:auto` (default) — `:dbms_metadata` on Oracle 12.1+, `:data_dictionary` otherwise. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modern SET_TRANSFORM_PARAM era), exposed as `use_dbms_metadata_dump?`. - `:dbms_metadata` — force the new path; raises `ArgumentError` on pre-12.1 (mirrors PR rsim#2576's `identifier_max_length: :long` fail-fast policy). - `:data_dictionary` — force the original implementation. Remains explicitly selectable on 12.1+ as well. Implementation walks `ALL_OBJECTS` per object_type rather than hand-rolling per-kind queries; this naturally picks up MATERIALIZED VIEW / TYPE / TRIGGER / SYNONYM / etc. without separate code paths. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here. Output is informed by `mysqldump --no-data` / `pg_dump --schema-only`: - `STORAGE` / `TABLESPACE` / `SEGMENT_ATTRIBUTES` / `EMIT_SCHEMA` suppressed via `DBMS_METADATA.SET_TRANSFORM_PARAM` so the dump is portable across installations and schemas. Oracle honors these for TABLE / INDEX / SEQUENCE. - `MATERIALIZED VIEW` DDL is an exception: Oracle does not honour the same suppression on MV (object_type-specific override does not work either), so MV DDL retains physical attributes (PCTFREE, TABLESPACE, SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is. - `REF_CONSTRAINTS` emitted as separate `ALTER TABLE … ADD CONSTRAINT` statements after all tables. - Inline constraints via Oracle's `CONSTRAINTS=TRUE` default; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice. - `STATEMENT_TOKEN` separation preserved (Oracle's `SQLTERMINATOR=FALSE` default) so `execute_structure_dump` splits as before. - `primary_key_trigger:` (rsim#2615) row triggers picked up via the TRIGGER object_type pass. - COMMENT ON TABLE / COMMENT ON COLUMN queried directly because `GET_DEPENDENT_DDL("COMMENT")` is unreliable. - `structure_dump_db_stored_code` selects 'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE' — `GET_DDL("PACKAGE", ...)` already returns spec + body, so selecting 'PACKAGE BODY' would emit the body twice. Specs follow the `pg_dump` / `mysqldump` testing convention: assert output shape rather than exact byte-level DDL. A schema-option spec verifies that `:schema` connection-time switching is honored (structure_dump walks `SYS_CONTEXT('userenv', 'current_schema')`, not the connecting user). The existing `structure_dump_spec` pins `:data_dictionary` in `before(:all)` so its exact-DDL assertions still apply to the legacy backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
be13c01 to
c567c23
Compare
This was referenced May 3, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a second
structure_dumpimplementation built on Oracle'sDBMS_METADATA.GET_DDL/GET_DEPENDENT_DDLalongside the existing data-dictionary (ALL_*) one. The new path lives in a separate file (lib/active_record/connection_adapters/oracle_enhanced/structure_dump/dbms_metadata.rb) — the existingstructure_dump.rbis untouched, so selecting:data_dictionaryretains exact master behavior.Selection
OracleEnhancedAdapter.structure_dump_methodaccepts::auto(default) — DBMS_METADATA on Oracle 12.1+, otherwise data-dictionary. The 12.1 floor is a project-policy version gate (the IDENTITY / EDITIONABLE / modernSET_TRANSFORM_PARAMera), exposed asuse_dbms_metadata_dump?.DBMS_METADATA.GET_DDLitself works on Oracle 9i+; this is a project decision, not a strict capability check.:dbms_metadata— force DBMS_METADATA. RaisesArgumentErroron pre-12.1 (mirrors PR Replace use_legacy_identifier_length with identifier_max_length (:auto default) #2576'sidentifier_max_length: :longfail-fast policy).:data_dictionary— force the implementation that assembles DDL from theALL_*static data dictionary views in Ruby. Remains explicitly selectable on 12.1+ as well.Implementation shape
The DBMS_METADATA path walks
ALL_OBJECTSper object_type rather than hand-rolling per-kind queries. This naturally picks upMATERIALIZED VIEW,TYPE,TRIGGER,SYNONYMand friends without separate code paths and minimizes future maintenance when Oracle adds new object kinds. FK constraints (REF_CONSTRAINT) are emitted in a single trailing block; cross-table dependency order is left to the user (or to a fresh-schema reload) rather than tracked here.Two modules in the same file split responsibilities so the dispatcher logic doesn't bleed into the DBMS_METADATA implementation:
Dispatcher—prepend-ed; routes the three entry points (structure_dump,structure_dump_db_stored_code,structure_dump_synonyms) to either DBMS_METADATA orsuper(data-dictionary).DbmsMetadata—include-d; the DBMS_METADATA implementation itself, with no dispatch logic.Output
Output shape is informed by
mysqldump --no-data/pg_dump --schema-only:STORAGE/TABLESPACE/SEGMENT_ATTRIBUTES/EMIT_SCHEMAsuppressed viaDBMS_METADATA.SET_TRANSFORM_PARAMso the dump is portable across installations and schemas. Oracle honors these forTABLE/INDEX/SEQUENCE.MATERIALIZED VIEWis an Oracle-side exception: even with object_type-specific overrides Oracle does not honour the suppression on MV, so MV DDL retains physical attributes (PCTFREE,TABLESPACE,SEGMENT CREATION, etc.) as Oracle emits them. Since MVs aren't creatable through Rails' standard migration helpers anyway, no post-processing pass is added — Oracle's output is used as-is.REF_CONSTRAINTSemitted as separateALTER TABLE … ADD CONSTRAINTstatements after all tables.CONSTRAINTS=TRUEdefault; UNIQUE backing indexes that DBMS_METADATA already inlines into the table DDL are filtered out so they aren't emitted twice.STATEMENT_TOKENseparation preserved (Oracle'sSQLTERMINATOR=FALSEdefault) soexecute_structure_dumpsplits as before.primary_key_trigger:(Restore trigger-based primary key generation as opt-in primary_key_trigger: option #2615) row triggers are picked up via theTRIGGERobject_type pass — addresses #2513 (comment).GET_DEPENDENT_DDL("COMMENT", ...)is unreliable.structure_dump_db_stored_codeselects'PROCEDURE', 'PACKAGE', 'FUNCTION', 'TYPE'only —GET_DDL("PACKAGE", ...)already returns spec + body, so selecting'PACKAGE BODY'would emit the body twice.SET_TRANSFORM_PARAMcalls whose value differs from Oracle's default are issued (STORAGE/TABLESPACE/SEGMENT_ATTRIBUTES/EMIT_SCHEMA/REF_CONSTRAINTS).Tests
A new
dbms_metadata_structure_dump_spec.rbexercises the new path with structural assertions only (CREATE TABLE/CREATE INDEX/ALTER TABLE … ADD CONSTRAINT/COMMENT ON …/ noSTORAGE/TABLESPACE/PCTFREEnoise) — exact byte-level DDL is intentionally not part of the contract, mirroring howpg_dump/mysqldumpare tested upstream. Regression coverage added for the unique-index and PACKAGE-BODY duplicate-output bugs found during development. A schema-option spec verifies that:schemaconnection-time switching is honored (the dump walksSYS_CONTEXT('userenv', 'current_schema'), not the connecting user).The existing
structure_dump_spec.rbpinsstructure_dump_method = :data_dictionaryinbefore(:all)and restores it inafter(:all), so the data-dictionary backend's exact-DDL assertions continue to run.Test plan
bundle exec rspec spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb— green (pinned to:data_dictionary)bundle exec rspec spec/active_record/connection_adapters/oracle_enhanced/dbms_metadata_structure_dump_spec.rb— greenbundle exec rspecfull suite — 747 examples, 0 failures, 12 pendingbundle exec rubocop— cleanCloses #2513.