Skip to content

fix(oracle): use configured catalog for constraint reflection - #31676

Merged
IceS2 merged 2 commits into
mainfrom
rca-comm-soft-delete-issue
Aug 20, 2026
Merged

fix(oracle): use configured catalog for constraint reflection#31676
IceS2 merged 2 commits into
mainfrom
rca-comm-soft-delete-issue

Conversation

@IceS2

@IceS2 IceS2 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

Related to #29997

Oracle ingestion can enumerate a table through DBA_TABLES while SQLAlchemy 2 reflects its constraints through ALL_CONSTRAINTS. For an ingestion account with catalog visibility but no direct table grants, that produces this per-table trace:

DBA_TABLES: 1 table → ALL_CONSTRAINTS: no table → NoSuchTableError → table omitted from the run

This change binds the connector catalog-aware query to the active SQLAlchemy 2 public reflection methods: get_pk_constraint, get_unique_constraints, and get_foreign_keys. The selected DBA or ALL prefix is therefore used consistently for table, column, and constraint discovery.

The constraint query now returns index_name so unique constraints retain SQLAlchemy compatible duplicates_index metadata.

Type of change:

  • Bug fix

High-level design:

N/A — small connector bug fix.

Tests:

Use cases covered

  • DBA_* table discovery and constraint reflection use the same catalog.
  • ALL_* table discovery and constraint reflection use the same catalog.
  • Primary keys, composite unique constraints, foreign keys, delete rules, and empty defaults are preserved.
  • Synonym resolution completes before identifier denormalization.

Unit tests

  • I added unit tests for the changed logic.
  • File added: ingestion/tests/unit/topology/database/test_oracle_constraint_reflection.py
  • 22 passed across the Oracle connector tests and the new regression tests.

Backend integration tests

  • Not applicable (no backend API changes).

Ingestion integration tests

  • No automated integration test was added.
  • The exact ingestion reflection boundary was validated against Oracle Free with DBA_TABLES=2 and ALL_TABLES=0; five columns, the primary key, a composite unique constraint, and a foreign key were returned.

Playwright (UI) tests

  • Not applicable (no UI changes).

Manual testing performed

  1. Ran the Oracle unit and regression tests: 22 passed.
  2. Ran the complete ingestion Ruff and formatting checks: 2661 files already formatted.
  3. Ran Basedpyright on the changed files: 0 errors.
  4. Ran SqlColumnHandlerMixin._get_columns_with_constraints against Oracle Free using catalog-only visibility and verified PK, unique, and FK metadata.

UI screen recording / screenshots:

Not applicable.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>.
  • My PR is linked to an open GitHub issue via Fixes #<issue-number>.
  • For JSON Schema changes: not applicable.
  • For UI changes: not applicable.
  • I have added tests and listed them above.
  • I have added a test that covers the exact scenario being fixed.

Greptile Summary

The PR redirects Oracle primary-key, unique-constraint, and foreign-key reflection through the connector’s configured DBA or ALL catalog and preserves backing-index metadata.

  • Adds catalog-aware implementations for SQLAlchemy’s public constraint reflection methods.
  • Extends the Oracle constraint query with index names.
  • Adds regression coverage for DBA/ALL catalogs, composite constraints, foreign keys, empty results, and direct synonym resolution.

Confidence Score: 4/5

The PR should not merge until synonym resolution remains enabled when preserve-case index reflection delegates to the new primary-key reflector.

The new primary-key implementation ignores the resolve_synonyms keyword used by the existing preserve-case index path, causing synonym-backed primary-key indexes to be emitted as ordinary indexes.

Files Needing Attention: ingestion/src/metadata/ingestion/source/database/oracle/utils.py

Important Files Changed

Filename Overview
ingestion/src/metadata/ingestion/source/database/oracle/metadata.py Rebinds SQLAlchemy’s public Oracle constraint reflection methods to catalog-aware connector implementations.
ingestion/src/metadata/ingestion/source/database/oracle/queries.py Adds the backing index name to each reflected constraint row.
ingestion/src/metadata/ingestion/source/database/oracle/utils.py Implements catalog-aware constraint reflection, but drops synonym resolution when the preserve-case index path forwards resolve_synonyms.
ingestion/tests/unit/topology/database/test_oracle_constraint_reflection.py Covers direct catalog and synonym reflection but omits the preserve-case index-to-PK synonym path.

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@IceS2
IceS2 requested a review from a team as a code owner August 18, 2026 07:02
Copilot AI lite review requested due to automatic review settings August 18, 2026 07:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Aug 18, 2026
Comment on lines +422 to +436
if kw.get("oracle_resolve_synonyms", False):
rows = list(
self._get_synonyms(
connection,
schema,
[table_name],
dblink,
info_cache=kw.get("info_cache"),
)
)
if rows:
row = rows[0]
table_name = self.denormalize_name(row.table_name)
schema = self.denormalize_name(row.table_owner)
if row.db_link:

@gitar-bot gitar-bot Bot Aug 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Edge Case: Synonym-not-found path skips name denormalization

In _prepare_constraint_args, when oracle_resolve_synonyms=True but self._get_synonyms returns no rows, table_name/schema are left as the raw normalized (typically lowercase) values instead of being denormalized. The constraint query (WHERE ac.table_name = :table_name AND ac.owner = :owner) then compares against Oracle's uppercased catalog values and returns nothing, silently dropping PK/UQ/FK metadata for non-synonym tables. SQLAlchemy's original _prepare_reflection_args falls back to denormalize_name(table_name)/denormalize_name(schema or default_schema_name) in this case. Add an else branch that denormalizes when rows is empty (mirroring the non-synonym branch); note the existing get_columns has the same gap so this only bites when synonym resolution is enabled. The new tests only cover the synonym-found case, so this path is untested.

Denormalize the identifiers when synonym resolution is on but no synonym matches.:

if kw.get("oracle_resolve_synonyms", False):
    rows = list(
        self._get_synonyms(connection, schema, [table_name], dblink, info_cache=kw.get("info_cache"))
    )
    if rows:
        row = rows[0]
        table_name = self.denormalize_name(row.table_name)
        schema = self.denormalize_name(row.table_owner)
        if row.db_link:
            dblink = row.db_link if row.db_link.startswith("@") else f"@{row.db_link}"
    else:
        table_name = self.denormalize_name(table_name)
        schema = self.denormalize_name(schema or self.default_schema_name)
else:
    table_name = self.denormalize_name(table_name)
    schema = self.denormalize_name(schema or self.default_schema_name)

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Restores catalog-aware constraint reflection in the Oracle connector to prevent missing tables during ingestion when using DBA visibility views. Consider handling name denormalization when the synonym-not-found path is skipped in _prepare_constraint_args.

💡 Edge Case: Synonym-not-found path skips name denormalization

📄 ingestion/src/metadata/ingestion/source/database/oracle/utils.py:422-436

In _prepare_constraint_args, when oracle_resolve_synonyms=True but self._get_synonyms returns no rows, table_name/schema are left as the raw normalized (typically lowercase) values instead of being denormalized. The constraint query (WHERE ac.table_name = :table_name AND ac.owner = :owner) then compares against Oracle's uppercased catalog values and returns nothing, silently dropping PK/UQ/FK metadata for non-synonym tables. SQLAlchemy's original _prepare_reflection_args falls back to denormalize_name(table_name)/denormalize_name(schema or default_schema_name) in this case. Add an else branch that denormalizes when rows is empty (mirroring the non-synonym branch); note the existing get_columns has the same gap so this only bites when synonym resolution is enabled. The new tests only cover the synonym-found case, so this path is untested.

Denormalize the identifiers when synonym resolution is on but no synonym matches.
if kw.get("oracle_resolve_synonyms", False):
    rows = list(
        self._get_synonyms(connection, schema, [table_name], dblink, info_cache=kw.get("info_cache"))
    )
    if rows:
        row = rows[0]
        table_name = self.denormalize_name(row.table_name)
        schema = self.denormalize_name(row.table_owner)
        if row.db_link:
            dblink = row.db_link if row.db_link.startswith("@") else f"@{row.db_link}"
    else:
        table_name = self.denormalize_name(table_name)
        schema = self.denormalize_name(schema or self.default_schema_name)
else:
    table_name = self.denormalize_name(table_name)
    schema = self.denormalize_name(schema or self.default_schema_name)
🤖 Prompt for agents
Code Review: Restores catalog-aware constraint reflection in the Oracle connector to prevent missing tables during ingestion when using DBA visibility views. Consider handling name denormalization when the synonym-not-found path is skipped in _prepare_constraint_args.

1. 💡 Edge Case: Synonym-not-found path skips name denormalization
   Files: ingestion/src/metadata/ingestion/source/database/oracle/utils.py:422-436

   In `_prepare_constraint_args`, when `oracle_resolve_synonyms=True` but `self._get_synonyms` returns no rows, `table_name`/`schema` are left as the raw normalized (typically lowercase) values instead of being denormalized. The constraint query (`WHERE ac.table_name = :table_name AND ac.owner = :owner`) then compares against Oracle's uppercased catalog values and returns nothing, silently dropping PK/UQ/FK metadata for non-synonym tables. SQLAlchemy's original `_prepare_reflection_args` falls back to `denormalize_name(table_name)`/`denormalize_name(schema or default_schema_name)` in this case. Add an `else` branch that denormalizes when `rows` is empty (mirroring the non-synonym branch); note the existing `get_columns` has the same gap so this only bites when synonym resolution is enabled. The new tests only cover the synonym-found case, so this path is untested.

   Fix (Denormalize the identifiers when synonym resolution is on but no synonym matches.):
   if kw.get("oracle_resolve_synonyms", False):
       rows = list(
           self._get_synonyms(connection, schema, [table_name], dblink, info_cache=kw.get("info_cache"))
       )
       if rows:
           row = rows[0]
           table_name = self.denormalize_name(row.table_name)
           schema = self.denormalize_name(row.table_owner)
           if row.db_link:
               dblink = row.db_link if row.db_link.startswith("@") else f"@{row.db_link}"
       else:
           table_name = self.denormalize_name(table_name)
           schema = self.denormalize_name(schema or self.default_schema_name)
   else:
       table_name = self.denormalize_name(table_name)
       schema = self.denormalize_name(schema or self.default_schema_name)

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

if dblink and not dblink.startswith("@"):
dblink = f"@{dblink}"

if kw.get("oracle_resolve_synonyms", False):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Synonym flag is dropped

When identifier-case preservation and synonym resolution are enabled, get_indexes_preserve_case forwards the setting as resolve_synonyms, but _prepare_constraint_args reads only oracle_resolve_synonyms. The primary-key lookup therefore queries the alias instead of its target, causing the PK-backed index to be reported as a regular index.

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit b54f18bff93ad3b6bb092783a16437e5aa6a3073 in Playwright run 32109555942, attempt 1.

✅ 110 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 48m 36s

⏱️ Max setup 3m 12s · max shard execution 13m 21s · max shard-job elapsed before upload 19m 53s · reporting 5s

🌐 212.82 requests/attempt · 1.79 app boots/UI scenario · 0.00% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 212.82 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 1.79 per UI scenario (216 boots / 121 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 46 0 0 0 0 0
✅ Shard ingestion-01 29 0 0 0 0 0
✅ Shard ingestion-02 35 0 0 0 0 0

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@IceS2 IceS2 added this to Shipping Aug 18, 2026
@IceS2 IceS2 moved this to In Review / QA 👀 in Shipping Aug 18, 2026
@IceS2
IceS2 added this pull request to the merge queue Aug 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-19T07:20:58Z)

Blocked the queue: playwright-summary

@IceS2
IceS2 added this pull request to the merge queue Aug 19, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-19T18:12:24Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright / playwright-ci (chromium-24)
  • py-tests — python / Unit Tests & Static Checks (3.12), python / Unit Tests & Static Checks (3.11), python / Unit Tests & Static Checks (3.10)

@IceS2
IceS2 added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit 1d28425 Aug 20, 2026
104 of 106 checks passed
@IceS2
IceS2 deleted the rca-comm-soft-delete-issue branch August 20, 2026 06:08
@github-project-automation github-project-automation Bot moved this from In Review / QA 👀 to Done ✅ in Shipping Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs

Projects

Status: Done ✅

Development

Successfully merging this pull request may close these issues.

3 participants