Skip to content

fix(dq): allow attaching test cases to nested columns - #36385

Open
TeddyCr wants to merge 14 commits into
open-metadata:mainfrom
TeddyCr:fix/nested-column-dq-backend
Open

TeddyCr wants to merge 14 commits into
open-metadata:mainfrom
TeddyCr:fix/nested-column-dq-backend

Conversation

@TeddyCr

@TeddyCr TeddyCr commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Fixes #36381 Backend slice of #36278: the API rejected column-level test cases on nested columns (<#E::table::…::columns::address.city>) with Invalid column name, because every test-case resolution site matched the entityLink's array field against top-level Table.columns only — while the storage format itself (nested Column.fullyQualifiedName dotted paths, FullyQualifiedName.getTableFQN, the UI's FqnPart.NestedColumn link writer) is already nested-aware.

What changed

  • ColumnUtil.resolveColumnByPath / validateColumnPath (new): resolve a dotted path by walking Column.children. FQN-split so a column literally named a.b arriving as "a.b" stays one segment; case-exact match first, case-insensitive fallback; a dotted literal column name arriving unquoted (legacy links) still resolves as a whole name when the segment walk fails; all keeps its meaning.
  • TestCaseRepository.validateColumnTestCase and validateDimensionColumns route through the new validation, so nested links attach via the API and a truly missing nested path still returns 400.
  • Tag inheritance (fetchAndSetTestSuiteTags bulk site + inheritTags) resolves nested columns, so tags on nested sub-columns now surface on their test cases.
  • CSV round-trip (convertFQNToEntityLink): exports the full quoted remaining FQN path (address.city, per-segment quoteName) instead of truncating nested FQNs to their first column segment.
  • No schema change, no migration (entityLink is an opaque string on the wire).
  • Decision record: ADR:2026-10-27-nested-column-dq-tests-address-nested-paths — segment-walk representation + per-dialect executability boundary.

Scope

Executing a test against a nested column is per-dialect and lands separately: ingestion (#36382: BigQuery/Databricks/Unity Catalog/Athena), Snowflake (#36383, needs structured-type ingestion first), UI picker (#36384).

How Has This Been Tested?

  • New ColumnUtilTest (11 tests): nested walk, multi-level nesting, case-insensitive + exact-case preference, quoted/unquoted dotted-name disambiguation, missing segments, all, and the 400 message. All green locally.
  • Regression: TestCaseRepositoryTest, TestSuiteRepositoryTest, ColumnRepositoryValidationTest — all green locally.
  • TestCaseResourceIT passed locally on MySQL + Elasticsearch: 360 passed, 16 skipped, no failures. The nested-column test now verifies tag inheritance through GET/list and programmatic result submission, history, and latest-result retrieval. The focused method also passed independently.
  • mvn spotless:apply clean; .github/java-tests/impact-map.json check passes.

Checklist

🤖 Generated with Claude Code


Summary by Gitar

  • PII masking:
    • Masks nested column profiles and test cases through PII tags on parent structs
    • Fails closed for profiles whose column no longer resolves
  • Type conformance:
    • Applies the supportedDataTypes check to nested column types
  • Ingestion runner:
    • Reports nested-column tests as unsupported instead of crashing
  • Dimension columns:
    • Keeps dimensionColumns top-level, since the runner only groups by top-level columns
  • Tag inheritance:
    • Resolves nested columns so sub-column tags surface on their test cases
  • CSV round-trip:
    • Exports full nested column paths instead of truncating to the first segment

This will update automatically on new commits.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; the previously reported test failure is fixed.

Summary

The PR lets the API attach test cases to nested columns.

  • Test-case links can point to nested columns and keep their full paths.
  • PII tags on nested columns also mask their profiles and test cases.
  • The runner reports nested-column tests it cannot execute.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Column test case link] --> B[Resolve quoted column path]
  B --> C[Walk column children]
  C --> D[Validate column and supported type]
  C --> E[Read column tags]
  E --> F[Mask sensitive profiles and test cases]
  D --> G[Save test case]
Loading

Reviews (12) · Last reviewed commit: "fix: address automated review findings (..." · Reviewed by Greptile

Follow-up integration verification

The tested worktree changes are now committed as 71da0c1370b01864502e615a2c08769a0766ffaf (no source changes after the run began). The planner captured the previous HEAD while the commit hooks were finishing, hence the generated report's uncommitted-changes notation. This run covers the follow-up test-only delta from e4e0fa01ce7e, not all production changes in the PR.

Local Java test run: PASSED

  • Commit: e4e0fa01ce7e (base e4e0fa01ce7e754d5a3b53b5a1fc6dd67378d85f) — uncommitted changes were present
  • Finished: 2026-10-28 18:12 UTC · 5.0 min
Step Scope Classes Tests Failed Skipped Min Result
integration mysql-elasticsearch · parallel 1 376 0 16 5.0 passed

Full local test details

Self-review: 71da0c1370b01864502e615a2c08769a0766ffaf, follow-up test-only delta reviewed against REVIEW.md; no substantive findings. Spotless and commit/push hooks passed.

Test case column validation, tag inheritance and the CSV FQN round-trip
all resolved the entityLink's column against top-level Table.columns only,
so nested paths like <#E::table::...::columns::address.city> were rejected
at creation and silently lost tags.

Resolve the column by walking Column.children with a quoted-aware dotted
path (case-exact first, case-insensitive fallback; a dotted literal column
name arriving unquoted still resolves). Route validateColumnTestCase,
dimensionColumns and both tag-resolution sites through it, and serialize
the full remaining FQN path on CSV export instead of truncating to the
first column segment.

Executing against the nested column remains per-dialect and is scoped to
PRs 2/2b (ingestion). ADR:2026-10-27-nested-column-dq-tests-address-nested-paths

Fixes open-metadata#36381
@TeddyCr
TeddyCr requested a review from a team as a code owner October 27, 2026 22:27
@TeddyCr
TeddyCr requested review from sonika-shah and a balanced review from Copilot October 27, 2026 22:27

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 insufficient remaining quota.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR checks passed

The linked issue has a description and all required Shipping project fields set. Thanks!

@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Oct 27, 2026
- [high] Nested types bypass the check (G-899c8c22f5)
- [medium] Nested-column test cases bypass the supported-data-type conformance check (F-1157ac5f81)
- [medium] resolveColumnByPath throws ParseCancellationException (HTTP 500) for legal column names that are not valid FQN paths (F-fcd10649ba)
- [medium] In TestCaseDefinitionConformance, resolve the column with the same nested-aware walk: (G-6763b25abc)
- [medium] Server errors pass this test (G-912bd95bae)
- [medium] Case variants never compete (G-dd0482246d)

Automated by pr-supervisor using glm-5.3-flash:cloud via ollama; reviewed by gpt-6.1-sol.
Copilot AI balanced review requested due to automatic review settings October 27, 2026 23:38

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 27, 2026
- [high] Nested tests are accepted but the ingestion runner raises StopIteration (F-0c43f7ed4f)
- [medium] CSV export fallback serializes a nested path as one quoted segment (F-4ae4313b5f)
- [medium] Package paths break Java style (G-d2d7790587)

Automated by pr-supervisor using glm-5.3-flash:cloud via ollama; reviewed by gpt-6.1-sol.
@TeddyCr
TeddyCr requested a review from a team as a code owner October 28, 2026 00:25
@TeddyCr
TeddyCr requested review from stout-yeoman and a balanced review from Copilot October 28, 2026 00:25

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
Comment thread ingestion/tests/unit/data_quality/test_test_case_runner.py
- [high] Nested-column test cases bypass PII masking (F-4817a6607f)
- [high] Missing-column test crashes (G-0f7298a958)
- [medium] New missing-column unit test crashes on a null fullyQualifiedName (F-19a6678346)
- [medium] Nested dimensionColumns are accepted but can never be evaluated by the ingestion runner (F-4902d80c56)

Automated by pr-supervisor using glm-5.3-flash:cloud via ollama; reviewed by gpt-6.1-sol.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 00:52

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 07:18

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
- [medium] Fail-closed column-profile masking can be skipped with a non-normalized column FQN (F-ce67264fb2)

Automated by pr-supervisor using glm-5.3-flash:cloud via ollama; reviewed by gpt-6.1-sol.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 07:56

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
- [high] New masking test fails (G-fd489e8a8d)
- [medium] Unauthorized for the first two calls, and query the PII column's own quoted FQN: (G-2843829498)

Automated by pr-supervisor using glm-5.3-flash:cloud via ollama; reviewed by gpt-6.1-sol.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 08:36

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 17:58

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 18:14

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 insufficient remaining quota.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs and removed safe to test Add this label to run secure Github workflows on PRs labels Oct 28, 2026

TeddyCr commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

The tested worktree changes are now committed as 71da0c1370b01864502e615a2c08769a0766ffaf (no source changes after the run began). The planner captured the previous HEAD while the commit hooks were finishing, hence the generated report's uncommitted-changes notation. This run covers the follow-up test-only delta from e4e0fa01ce7e, not all production changes in the PR.

Local Java test run: PASSED — details

  • Commit: e4e0fa01ce7e (base e4e0fa01ce7e754d5a3b53b5a1fc6dd67378d85f) — uncommitted changes were present
  • The summary is in the PR description.

Tests run locally

  • integration · mysql-elasticsearch · parallel: BaseEntityIT$ListEntityHistoryByTimestampPaginationTest (360 passed, 16 skipped), TestCaseResourceIT (0 passed)

Per-class counts come from the test reports, which can credit a test to the wrong class when classes run concurrently, and keep only the last run of a nested class that several test classes inherit. The summary's step totals are Maven's own counts.

Why these tests
  • changed test ← openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TestCaseResourceIT.java
Commands
mvn -B verify -pl :openmetadata-integration-tests -am -Dspring-boot.repackage.skip=true -Pmysql-elasticsearch -DintegrationTests.lane=parallel -Dit.test=TestCaseResourceIT -Dfailsafe.failIfNoSpecifiedTests=false -Dtest=NoUnitTestsInThisRun -Dsurefire.failIfNoSpecifiedTests=false

Focused verification also passed: TestCaseResourceIT#post_nestedColumnLevelTest_200_OK (1 passed). The test POSTs a result for address.city (HTTP 201), reads result history (HTTP 200), and checks timestamp/status/message plus nested-column tags in GET and list responses. spotless:apply, spotless:check, pre-commit and pre-push hooks passed.

@gitar-bot

gitar-bot Bot commented Oct 28, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 4 closed / 4 findings

🔴 High risk · Expanding PII masking to nested profiles and test cases changes sensitive-data access decisions; a path-resolution error could expose tagged data or hide permitted data.

Adds nested-column support for DQ test cases, so entityLinks like <#E::table::…::columns::address.city> resolve through Column.children instead of failing with Invalid column name. Resolved this session: the data-type conformance check now applies to nested columns, dimensionColumns stay top-level per the accepted ADR, PII tags on ancestor structs mask nested children's profiles and test cases, and the PII-masking test assertion was corrected. No open findings.

✅ 4 closed
✅ Bug: Data-type conformance check is silently skipped for nested columns

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java:1118 📄 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TestCaseResourceIT.java:711 📄 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TestCaseResourceIT.java:778-788 📄 docs/decisions/2026-10-27-nested-column-dq-tests-address-nested-paths.md:34-35
TestCaseDefinitionConformance.validateColumnType still finds the column with column(table, entityLink.getArrayFieldName()), which only searches top-level table.getColumns() (lines ~45-56). Its own comment says it "must find the same one" as validateColumnTestCase. This PR changes validateColumnTestCase to walk nested paths, so a link like columns::address.city now passes validation, but the conformance lookup comes back empty and type.isPresent() is false. As a result, any test definition can be attached to any nested column whatever its supportedDataTypes are. The new IT shows this: post_nestedColumnLevelTest_200_OK attaches columnValuesToBeBetween (a numeric test) to a VARCHAR address.city and expects 200.

This also goes against the new record's own Consequences section (ADR:2026-10-27-nested-column-dq-tests-address-nested-paths), which says anything that validates or resolves a test case's column must use the segment walk. The fix is to resolve through ColumnUtil.resolveColumnByPath. Then switch the IT to a test that fits a string column, or make the leaf numeric.

✅ Quality: Top-level dimensionColumns contradicts the PR's own Accepted ADR

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java:1126-1129 📄 docs/decisions/2026-10-27-nested-column-dq-tests-address-nested-paths.md:28-30 📄 docs/decisions/2026-10-27-nested-column-dq-tests-address-nested-paths.md:40-41
This commit puts validateDimensionColumns back on the top-level-only validateColumn(table, dimensionColumn, Boolean.FALSE). The PR's own Accepted record says otherwise. Its Decision section in ADR:2026-10-27-nested-column-dq-tests-address-nested-paths states: "The same resolution governs the definition's supportedDataTypes conformance check, dimensionColumns and the tag inheritance of column-scoped test cases". Its Consequences section adds that anything validating a test case's column "must use the segment walk, not a top-level Stream over Table.columns".

The code comment gives a real reason: the ingestion runner resolves dimensions with SQAValidatorMixin.get_column_from_list against top-level columns only. That reason belongs in the record. As things stand, the tree says one thing in the ADR and does another in the code.

Fix: add a v3 revision to the ADR. Remove dimensionColumns from the list of things the segment walk governs, and add this rule: "dimensionColumns resolve against top-level Table.columns only (case-insensitive) until the runner can group by nested paths." Alternatively, keep the nested walk and change the runner instead.

✅ Security: Ancestor PII tag masks the column but not its nested children

📄 openmetadata-service/src/main/java/org/openmetadata/service/security/mask/PIIMasker.java:209-215 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/mask/PIIMasker.java:229-238
In getSingleColumnProfile, a PII.Sensitive tag on an ancestor masks the requested column itself. The subtree walk that follows, maskColumnProfiles(columns), then calls maskNestedColumnProfiles(columns, false), so ancestorSensitive starts out false. Because the requested column has no direct tag, its children get ancestorSensitive=false and keep their profiles. Example: address is tagged PII and an unauthorized user requests GET …/columns/name/<table>.address.zip?fields=profile. zip comes back masked, but zip.code, which setColumnProfile filled in during the flattened walk, still returns its full profile. The full-table walk would mask it. The Javadoc says the ancestor tag should mask the column "the way the full-tree walk masks the subtree beneath it", and this code doesn't. The fix is to pass the ancestor result into the walk so it covers the whole subtree, and to add a test where the requested column has a profiled child.

✅ Bug: New test's PII-masking assertion cannot pass: wrong FQN and stub order

📄 openmetadata-service/src/test/java/org/openmetadata/service/security/mask/PIIMaskerTest.java:732 📄 openmetadata-service/src/test/java/org/openmetadata/service/security/mask/PIIMaskerTest.java:752-759 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/mask/PIIMasker.java:248-253
In getColumnProfileResolvesNonNormalizedColumnFqnsAgainstTheRealColumn, the stub thenReturn(false, true) makes authorizePII return true on the second and later calls. getColumnProfile calls authorizePII exactly once (PIIMasker.java:248), so the second call, which expects masking, runs as an authorized user and gets piiProfiles back unchanged. That call also still passes quotedServiceFqn, which ends in .city. getColumnProfile decides whether to mask from the column FQN, not from ColumnProfile.name, so it resolves the untagged city column and would not mask even for an unauthorized caller. As a result, assertNull(masked.getFirst().getName()) gets "email" and the test fails. The fix is to point the second call at "service".db.schema.orders.email and stub thenReturn(false, false, true).

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Display: compact → Showing compact verification results.

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

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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

None yet

Development

Successfully merging this pull request may close these issues.

Backend: allow attaching DQ test cases to nested columns (validate + tag inheritance)

2 participants