Skip to content

Fixes 29997: Oracle ingestion get_pk_constraint NoSuchTableError for tables without a primary key#30206

Open
zerafachris wants to merge 4 commits into
open-metadata:mainfrom
zerafachris:fix/oracle-pk-constraint-nosuchtableerror
Open

Fixes 29997: Oracle ingestion get_pk_constraint NoSuchTableError for tables without a primary key#30206
zerafachris wants to merge 4 commits into
open-metadata:mainfrom
zerafachris:fix/oracle-pk-constraint-nosuchtableerror

Conversation

@zerafachris

@zerafachris zerafachris commented Jul 18, 2026

Copy link
Copy Markdown

Describe your changes:

Fixes #29997

I worked on this because the Oracle connector's metadata ingestion fails on every table that lacks a primary key, after the SQLAlchemy 1.4 → 2.0 migration in #26031.

Root cause: Under SQLAlchemy 2.0, Oracle's Inspector.get_pk_constraint() is a thin wrapper over the new bulk get_multi_pk_constraint() API. That API only returns dict entries for tables that have at least one PK-constraint row; a table without a PK is simply absent, and the wrapper's _value_or_raise then raises NoSuchTableError for "absent from dict" even though the table exists. Under SQLAlchemy 1.4, the same call returned an empty dict instead of raising. _get_columns_with_constraints in sql_column_handler.py calls get_pk_constraint with no exception handling (unlike the adjacent get_unique_constraints/get_foreign_keys calls, which already tolerate NotImplementedError), so this regression aborts ingestion for every table without a PK.

What changed and why: Wrapped the get_pk_constraint call in a try/except NoSuchTableError, treating it the same way as "no PK constraint" (empty dict), matching the existing pattern used for unique/foreign key constraints in the same function. By the time this call runs, the table itself is already known to exist (columns were already reflected), so a NoSuchTableError here can only mean "no PK data," not "table missing."

Type of change:

  • Bug fix

Tests:

Use cases covered

  • Oracle ingestion (and any other dialect that raises NoSuchTableError from get_pk_constraint for a table without a primary key) no longer aborts column/constraint extraction for that table; it now returns an empty PK column list, matching pre-regression behavior.

Unit tests

  • I added unit tests for the new/changed logic.
  • Files added/updated: ingestion/tests/unit/topology/database/test_oracle.py (test_get_columns_with_constraints_handles_no_pk_nosuchtableerror)
  • The new test mocks Inspector.get_pk_constraint to raise NoSuchTableError and asserts SqlColumnHandlerMixin._get_columns_with_constraints returns an empty PK column list instead of propagating the exception. Verified the test fails against the pre-fix code and passes after the fix.

Manual testing performed

  1. pytest ingestion/tests/unit/topology/database/test_oracle.py -v — all 15 tests pass (14 pre-existing + 1 new).
  2. ruff check / ruff format --check on the touched files — clean.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #<issue-number> above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.
  • I have added a test that covers the exact scenario we are fixing.

Prepared with AI assistance (Claude Code, Anthropic), reviewed for correctness before submission.

Greptile Summary

This PR prevents Oracle metadata ingestion from failing on tables without primary keys. The main changes are:

  • Adds an Oracle-specific fallback for ambiguous primary-key reflection errors.
  • Checks table existence before treating the error as an empty primary key.
  • Extracts a shared helper for connector-specific primary-key handling.
  • Adds tests for existing PK-less tables and genuinely missing tables.

Confidence Score: 5/5

This looks safe to merge.

  • Missing Oracle tables continue through the existing table failure path.
  • Existing Oracle tables without primary keys now produce an empty primary-key list.
  • Other connectors retain the default reflection behavior.
  • Tests cover both branches of the new existence check.

Important Files Changed

Filename Overview
ingestion/src/metadata/ingestion/source/database/oracle/metadata.py Adds Oracle-specific handling that distinguishes PK-less tables from missing tables.
ingestion/src/metadata/ingestion/source/database/sql_column_handler.py Extracts primary-key reflection into an overridable instance method.
ingestion/tests/unit/topology/database/test_oracle.py Tests both outcomes of the new Oracle table-existence check.

Reviews (4): Last reviewed commit: "fix: disambiguate missing table from no-..." | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)

… without a PK (open-metadata#29997)

Under SQLAlchemy >= 2.0, Oracle's get_pk_constraint raises NoSuchTableError
for tables that have no primary key, instead of returning an empty dict as
it did under SQLAlchemy 1.4. This aborts ingestion for every table lacking
a PK. Wrap the call the same way the adjacent unique/foreign key constraint
calls are already wrapped.

Prepared with AI assistance (Claude Code, Anthropic), reviewed for correctness before submission.
@zerafachris
zerafachris requested a review from a team as a code owner July 18, 2026 16:57
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

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

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

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/ingestion/source/database/sql_column_handler.py Outdated
…mixin

Per review feedback: the previous fix caught NoSuchTableError in the generic
SqlColumnHandlerMixin, used by every SQL connector, before columns are even
reflected — so for dialects where NoSuchTableError still means "table is
genuinely missing", the error would be silently swallowed as "no PK" instead
of surfacing.

Move the fix into an Oracle-only override of _get_columns_with_constraints in
OracleSource, since Oracle's get_multi_pk_constraint-based get_pk_constraint is
the dialect confirmed to conflate "no PK" with "absent from result" under
SQLAlchemy >= 2.0. Other dialects go through the untouched mixin, where a
NoSuchTableError continues to propagate as a real "table not found" signal.

Updated the regression test to exercise OracleSource's override directly.
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/ingestion/source/database/oracle/metadata.py Outdated
Per review feedback: the Oracle-only override of _get_columns_with_constraints
was a near-verbatim ~75-line copy of SqlColumnHandlerMixin's implementation,
differing only in the try/except around the PK fetch. Any future change to
the shared unique/foreign-key handling would silently not reach Oracle.

Split SqlColumnHandlerMixin._get_columns_with_constraints so the PK fetch is
its own small method, _get_pk_constraint. OracleSource now overrides only
that method; the rest of the constraint-assembly logic (unique/foreign keys,
column-name cleanup) is inherited unchanged from the shared mixin via normal
method dispatch, matching the existing super()-delegation pattern already
used by BigQuery/Cockroach for _get_columns_with_constraints.
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/ingestion/source/database/oracle/metadata.py
…oSuchTableError

Per review feedback: catching NoSuchTableError from get_pk_constraint doesn't
distinguish "table has no PK" from "table was dropped/renamed after listing but
before this per-table reflection ran" — both raise the same exception. Silently
swallowing it in the latter case would let yield_table emit stale zero-column
metadata for a table that no longer exists, instead of reporting the failure.

_get_pk_constraint now confirms via inspector.has_table() (a dedicated Oracle
data-dictionary existence check, independent of the buggy bulk PK-lookup query)
before treating NoSuchTableError as "no PK". If the table is confirmed gone, the
error is re-raised and propagates up to yield_table's existing exception handler,
which correctly reports it as a failed/missing table via StackTraceError.

Added a test for the re-raise path alongside the existing no-PK path.
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@gitar-bot

gitar-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Resolves Oracle ingestion failures for tables lacking primary keys by wrapping get_pk_constraint in NoSuchTableError handling and extracting the logic into a reusable helper. No issues found.

✅ 1 resolved
Quality: Oracle override duplicates ~75 lines of the base mixin

📄 ingestion/src/metadata/ingestion/source/database/oracle/metadata.py:142-156
OracleSource._get_columns_with_constraints is a near-verbatim copy of SqlColumnHandlerMixin._get_columns_with_constraints, differing only in the try/except NoSuchTableError around get_pk_constraint. Any future fix to unique/foreign-key handling in the base will silently not reach Oracle, causing behavioral drift. Consider overriding only the PK fetch (e.g., a small helper that returns pk_constraints, wrapped in try/except) and delegating the rest to super(), rather than copying the whole method.

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 👍 / 👎 | Gitar

@harshach harshach added safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch labels Jul 18, 2026
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (31 flaky)

✅ 4540 passed · ❌ 0 failed · 🟡 31 flaky · ⏭️ 95 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 436 0 4 16
✅ Shard 2 11 0 0 0
🟡 Shard 3 817 0 15 8
🟡 Shard 4 822 0 1 18
🟡 Shard 5 838 0 3 5
🟡 Shard 6 785 0 3 46
🟡 Shard 7 831 0 5 2
🟡 31 flaky test(s) (passed on retry)
  • Flow/TestConnectionModal.spec.ts › failure state shows Edit Connection button and Retry Test button (shard 1, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: metric (shard 1, 1 retry)
  • Pages/SearchSettings.spec.ts › Latest preview config wins when a superseded request resolves late (shard 1, 1 retry)
  • Flow/SearchRBAC.spec.ts › the browse tree only shows the asset-type categories a user can access (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary Term (Nested) (shard 3, 1 retry)
  • Features/BulkEditOperationBadges.spec.ts › Database service bulk edit search filters rows and clear restores them (shard 3, 1 retry)
  • Features/BulkImportWithDotInName.spec.ts › Full import cycle with dot in service name (shard 3, 1 retry)
  • Features/ContextCenterArchive.spec.ts › archive page lazy-loads more rows on scroll within its own scroll container (shard 3, 2 retries)
  • Features/ContextCenterArticles.spec.ts › Article listing search filters, clears, and shows empty state (shard 3, 1 retry)
  • Features/ContextCenterArticles.spec.ts › Article edit persistence and unsaved title behavior are correct (shard 3, 2 retries)
  • Features/ContextCenterDocument.spec.ts › move document to folder via card menu shows folder name on the card (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › cancel button in edit mode closes the modal without saving (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › adding a linked asset in edit mode shows entity badge on the row (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › Private memory shows "visible only to you" description (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › data consumer sees a read-only modal for shared memories they do not own (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › ArrowDown + Enter keyboard navigation selects the linked table result (shard 3, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with editAll permission can see restore action but not delete action on an archived document, and can restore it (shard 3, 1 retry)
  • Features/Glossary/GlossaryAdvancedOperations.spec.ts › should change domain on glossary (shard 3, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should drag nested term to root level of same glossary (shard 3, 1 retry)
  • Features/Glossary/GlossaryTermRelationsGraphNested.spec.ts › viewing a child term: parent appears as a 1-hop neighbour via parentOf edge (shard 4, 1 retry)
  • Features/TierDropdown.spec.ts › should show enabled tier tag in dropdown (shard 5, 1 retry)
  • Flow/ObservabilityAlerts.spec.ts › Data Contract Name filter lists matching data contracts (shard 5, 1 retry)
  • Pages/CustomProperties.spec.ts › Should search custom properties for dashboard in right panel (shard 5, 1 retry)
  • Pages/Entity.spec.ts › Announcement create, edit & delete (shard 6, 1 retry)
  • Pages/ExploreBrowse.spec.ts › service type drill-down disables unrelated roots and query-panel Clear resets it (shard 6, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary CSV import preserves typed relations (shard 7, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for apiService in platform lineage (shard 7, 1 retry)
  • ... and 1 more

📦 Download artifacts

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Oracle ingestion: get_pk_constraint raises NoSuchTableError for tables without a primary key (regression from SQLAlchemy 2.0 migration, #26031)

2 participants