Skip to content

Fixes 33531: keep dedicated-mapper fields in the RDF indexing field set - #33533

Merged
harshach merged 2 commits into
mainfrom
harshach/fix-rdf-dedicated-mapper-fields
Sep 17, 2026
Merged

harshach merged 2 commits into
mainfrom
harshach/fix-rdf-dedicated-mapper-fields

Conversation

@harshach

@harshach harshach commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

Fixes #33531

Salvaged from the closed PR #33248 (tracking: #33528).

RdfIndexingFields.forSupportedFields chose which fields to load for RDF indexing by reusing
RdfPropertyMapper.isIgnoredEntityField. But IGNORED_PROPERTIES mixes two different reasons for
skipping a field — and its own comment says so:

  1. audit/helper data with no place in the graph: changeDescription, votes, testCaseResult,
    identityProviderSubject;
  2. fields a dedicated structured-emission step owns, which "must not also be written as opaque
    JSON literals": tableConstraints (emitTableConstraints), profile (RdfQualityMapper DQV
    measurements), pipelineStatus (RdfActivityMapper prov:Activity), usageSummary
    (RdfUsageMapper usage counts).

Category 2 still has to be fetched. Reusing the predicate dropped all four from the entity before
their mappers ever ran, so table constraints, table profiles, pipeline status and usage summaries
never reached the knowledge graph at all.

This keeps an explicit DEDICATED_MAPPER_FIELDS set in RdfIndexingFields and retains those four,
leaving category 1 filtered out exactly as before.

Type of change:

  • Bug fix

High-level design:

N/A — small bug fix. The two categories now have two predicates instead of one overloaded one.

Tests:

Use cases covered

  • A reindexed Table carries its constraints and profile into RDF; a Pipeline carries its
    pipelineStatus; usage summaries reach RdfUsageMapper.
  • changeDescription, votes and testCaseResult are still excluded.

Unit tests

RdfIndexingFieldsTest.retainsInputsOfDedicatedRdfMappers, which fails on main — today
forSupportedFields returns [] for those four fields:

expected: <[pipelineStatus, profile, tableConstraints, usageSummary]> but was: <[]>

After the fix:

Tests run: 38, Failures: 0, Errors: 0  -- RdfIndexingFieldsTest + RdfPropertyMapperTest

The pre-existing test in the same class (which asserts the category-1 fields stay filtered) is
unchanged and still passes.

Backend integration tests

Not applicable — no API change. docs/rdf-ontology-contract.md describes the predicate contract
these mappers emit.

Ingestion integration tests

Not applicable.

Playwright (UI) tests

Not applicable — no UI change.

Manual testing performed

  1. mvn -pl openmetadata-service test -Dtest='RdfIndexingFieldsTest,RdfPropertyMapperTest' — 38 tests, BUILD SUCCESS.
  2. Same command with the production hunk reverted — retainsInputsOfDedicatedRdfMappers fails with the output above.
  3. mvn spotless:apply -pl openmetadata-service — 3032 files clean, 0 changed.

Note for reviewers: this changes what is fetched for RDF indexing, so existing graphs need a
re-index to pick up the previously-missing triples.

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 a GitHub issue via Fixes #33531.
  • I have commented on my code, particularly in hard-to-understand areas.
  • 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 we are fixing.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge because the revised predicate restores required mapper inputs without exposing the fields intentionally excluded from RDF.

Summary

This PR separates RDF field-loading eligibility from generic property-emission eligibility.

  • Retains tableConstraints, profile, pipelineStatus, and usageSummary while loading entities for RDF indexing.
  • Continues excluding audit, time-series, and identity-correlator fields that do not belong in the graph.
  • Adds regression coverage for field selection and structured table-constraint emission.

Reviews (2) · Last reviewed commit: "Make RdfPropertyMapper the single source..."

RdfIndexingFields picked the fields to load by reusing
RdfPropertyMapper.isIgnoredEntityField, but IGNORED_PROPERTIES mixes two
reasons for skipping a field: audit data with no place in the graph
(changeDescription, votes, testCaseResult, identityProviderSubject), and
fields a dedicated structured-emission step owns and that must therefore not
also be written as opaque JSON literals (tableConstraints -> emitTableConstraints,
profile -> RdfQualityMapper, pipelineStatus -> RdfActivityMapper,
usageSummary -> RdfUsageMapper).

The second group still has to be fetched. Reusing the predicate dropped all
four before their mappers ever ran, so table constraints, profiles, pipeline
status and usage never reached the graph.

RdfIndexingFieldsTest.retainsInputsOfDedicatedRdfMappers fails on main today:
forSupportedFields returns [] for those four fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@harshach
harshach requested a review from a team as a code owner September 17, 2026 14:21
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Sep 17, 2026
@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.

…tion

Review feedback: the DEDICATED_MAPPER_FIELDS set added to RdfIndexingFields
duplicated a subset of RdfPropertyMapper.IGNORED_PROPERTIES, and the two would
have to be kept in sync by hand - a new dedicated-mapper field added to
IGNORED_PROPERTIES alone would silently reintroduce this bug.

Remove the duplication instead. IGNORED_PROPERTIES was always two sets wearing
one name - its own comment said so - so split it along that seam:

  NON_GRAPH_PROPERTIES       audit data, inline time-series and auth material
                             that must never be fetched or emitted
  DEDICATED_MAPPER_PROPERTIES  fields that DO belong in the graph but are
                             emitted by emitStructuredProperties, so the
                             generic loop must not also write them as JSON

IGNORED_PROPERTIES is now the union, so isIgnoredEntityField is unchanged, and
the new isIndexableEntityField answers the different question RdfIndexingFields
actually asks: must this field be loaded before translation? There is no second
set to drift.

Also adds two tests pinning the emitter contract that motivates all of this:
with tableConstraints present, emitStructuredProperties produces om:hasConstraint
relations; with it absent it produces nothing at all, silently. Those pass on
main too - they characterise the starvation. The regression test for the bug
itself remains RdfIndexingFieldsTest.retainsInputsOfDedicatedRdfMappers, which
fails on main with `expected: <[pipelineStatus, profile, tableConstraints,
usageSummary]> but was: <[]>`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@harshach

Copy link
Copy Markdown
Collaborator Author

Good catch — addressed in a939977, and it's worth answering the second question too, because it's the whole point of the change.

We do create the relations — they were being starved

tableConstraints, profile, pipelineStatus and usageSummary all get proper structured RDF, not JSON blobs. RdfPropertyMapper.emitStructuredProperties dispatches each to a dedicated emitter:

RdfJsonNode.array(entityJson, "tableConstraints")
    .ifPresent(c -> emitTableConstraints(c, entity.getFullyQualifiedName(), entityResource, model));
RdfJsonNode.object(entityJson, "profile")
    .ifPresent(p -> RdfQualityMapper.emitTableProfile(p, entityResource, model));
RdfJsonNode.object(entityJson, "pipelineStatus")
    .ifPresent(s -> RdfActivityMapper.emitPipelineActivity(s, entity.getFullyQualifiedName(), entityResource, baseUri, model));
RdfJsonNode.object(entityJson, "usageSummary")
    .ifPresent(u -> RdfUsageMapper.emitUsageSummary(u, entityResource, model));

emitTableConstraints alone mints om:TableConstraint resources and emits om:hasConstraint, om:hasConstrainedColumn, om:hasReferredColumn, om:references, om:relationshipType, om:isUnique, om:isPrimaryKey — including the FK edges SPARQL traverses. That is exactly why these four are excluded from the generic field loop: they must not also be written as opaque JSON literals.

The bug is one layer upstream, in what gets fetched. RdfIndexingFields.forEntityType feeds Entity.getFields(...) at RdfIndexApp:998, RdfPartitionWorker:420 and RdfRepository:511 — it decides which fields are loaded from the database before translation. It picked them with isIgnoredEntityField, so those four were never loaded. Then:

static Optional<JsonNode> field(JsonNode parent, String fieldName) {
  return object(parent).map(value -> value.get(fieldName)).filter(value -> !nullOrEmpty(value));
}

entityJson.get("tableConstraints") is null → Optional.empty().ifPresent(...) never fires. The dedicated mappers run on every entity and emit nothing, silently. No error, no warning, just missing triples.

So: the relations are built correctly; the data never reached the builder.

On the duplication — you're right, and it's now gone

Rather than exposing a second constant to keep in sync, I removed the second set entirely. IGNORED_PROPERTIES was always two sets wearing one name — its own comment enumerated the categories — so I split it along that existing seam:

// never fetched, never emitted: audit data, inline time-series, auth material
private static final Set<String> NON_GRAPH_PROPERTIES =
    Set.of("changeDescription", "votes", "testCaseResult", "identityProviderSubject");

// fetched and emitted, but by emitStructuredProperties rather than the generic loop
private static final Set<String> DEDICATED_MAPPER_PROPERTIES =
    Set.of("tableConstraints", "profile", "pipelineStatus", "usageSummary");

private static final Set<String> IGNORED_PROPERTIES =
    Stream.concat(NON_GRAPH_PROPERTIES.stream(), DEDICATED_MAPPER_PROPERTIES.stream())
        .collect(Collectors.toUnmodifiableSet());

isIgnoredEntityField keeps its exact meaning and its callers are untouched. RdfIndexingFields now asks the question it actually meant to ask:

return supportedFields.stream().filter(RdfPropertyMapper::isIndexableEntityField).sorted().toList();

There is no set to keep in sync: add a field to DEDICATED_MAPPER_PROPERTIES and it is automatically still fetched; add one to NON_GRAPH_PROPERTIES and it is automatically excluded from both. The drift you described can't happen.

Tests

Two tests added to RdfPropertyMapperTest pin the emitter contract above — with tableConstraints present, om:hasConstraint relations appear; with it absent, nothing does. Being precise: those two pass on main as well, because the emitter was never the broken part — they characterise the starvation rather than catch the bug.

The regression test for the bug is still RdfIndexingFieldsTest.retainsInputsOfDedicatedRdfMappers, and it fails on main:

expected: <[pipelineStatus, profile, tableConstraints, usageSummary]> but was: <[]>

Full run after the change: 53 tests green across RdfIndexingFieldsTest, RdfPropertyMapperTest, RdfUsageMapperTest, JsonLdTranslatorTest and RdfContextRegistryTest — including the pre-existing cases that assert changeDescription, votes and testCaseResult stay out of the graph, which is what proves the union is intact.

@gitar-bot

gitar-bot Bot commented Sep 17, 2026

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

Splits IGNORED_PROPERTIES into two explicit sets to fix RDF field fetching for dedicated mappers: tableConstraints, profile, pipelineStatus, and usageSummary are now retained while audit and helper fields remain filtered. Unit tests confirm the fix and validate that existing exclusions still apply.

✅ 1 closed
Quality: DEDICATED_MAPPER_FIELDS duplicates a subset of IGNORED_PROPERTIES

📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfIndexingFields.java:22-26
DEDICATED_MAPPER_FIELDS in RdfIndexingFields hardcodes the same four category-2 fields (tableConstraints, profile, pipelineStatus, usageSummary) that live inside RdfPropertyMapper.IGNORED_PROPERTIES. The two sets must stay in sync manually: if a future dedicated-mapper field is added to IGNORED_PROPERTIES without also being added here, it will silently be dropped from RDF indexing again — reintroducing exactly the bug this PR fixes. Consider exposing the dedicated-mapper subset from RdfPropertyMapper (e.g. a DEDICATED_MAPPER_FIELDS constant with an accessor) so there is a single source of truth.

Review coverage

Rules No rules evaluated

Functional validation Not enabled · Set up

Auto-approval Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit a9399777e5fee5f7d3e646ea5aa91ed177e96ffe in Playwright run 35236826508, attempt 1.

✅ 4507 passed · ❌ 0 failed · 🟡 8 flaky · ⏭️ 1 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) 54m 16s

⏱️ Max setup 5m 9s · max shard execution 23m 10s · max shard-job elapsed before upload 26m 31s · reporting 16s

🌐 219.21 requests/attempt · 2.23 app boots/UI scenario · 41.37% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 41.37% (convergence target: at most 15%).
  • Browser traffic was 219.21 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.23 per UI scenario (10747 boots / 4812 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard advanced-search-01 130 0 0 0 0 0
✅ Shard chromium-01 163 0 0 0 0 0
✅ Shard chromium-02 165 0 0 0 0 0
✅ Shard chromium-03 177 0 0 0 0 0
✅ Shard chromium-04 230 0 0 0 0 0
🟡 Shard chromium-05 168 0 1 0 0 0
✅ Shard chromium-06 194 0 0 0 0 0
🟡 Shard chromium-07 164 0 1 0 0 0
✅ Shard chromium-08 171 0 0 0 0 0
✅ Shard chromium-09 168 0 0 1 0 0
✅ Shard chromium-10 163 0 0 0 0 0
🟡 Shard chromium-11 179 0 1 0 0 0
🟡 Shard chromium-12 159 0 1 0 0 0
🟡 Shard chromium-13 189 0 1 0 0 0
✅ Shard chromium-14 178 0 0 0 0 0
🟡 Shard chromium-15 186 0 1 0 0 0
✅ Shard chromium-16 215 0 0 0 0 0
🟡 Shard chromium-17 210 0 1 0 0 0
✅ Shard chromium-18 168 0 0 0 0 0
🟡 Shard chromium-19 200 0 1 0 0 0
✅ Shard chromium-20 164 0 0 0 0 0
✅ Shard chromium-21 168 0 0 0 0 0
✅ Shard chromium-22 166 0 0 0 0 0
✅ Shard data-asset-rules-01 65 0 0 0 0 0
✅ Shard domain-isolation-01 16 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 80 0 0 0 0 0
✅ Shard import-export-02 70 0 0 0 0 0
✅ Shard ingestion-01 43 0 0 0 0 0
✅ Shard ingestion-02 55 0 0 0 0 0
✅ Shard reindex-01 28 0 0 0 0 0
✅ Shard search-01 12 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 8 flaky test(s) (passed on retry)
  • Pages/DataContractsSemanticRules.spec.tsValidate Domain Rule Is (shard chromium-05, 1 retry)
  • Features/ContextCenterArchive.spec.tsfull document lifecycle: folder expand icon, upload, delete, restore, and permanent delete (shard chromium-07, 1 retry)
  • Pages/DescriptionVisibility.spec.tsCustomized Table detail page Description widget shows long description (shard chromium-11, 1 retry)
  • Features/Glossary/GlossaryAdvancedOperations.spec.tsshould show error when glossary name exceeds limit (shard chromium-12, 1 retry)
  • Pages/Tag.spec.tsVerify Owner Add Delete (shard chromium-13, 1 retry)
  • Flow/CustomizeWidgets.spec.tsKPI Widget (shard chromium-15, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.tsShould remove user owner for knowledgeCenter (shard chromium-17, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.tsColumn lineage for dashboard -> container (shard chromium-19, 1 retry)

📦 Download artifacts

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

@harshach
harshach merged commit 090b5af into main Sep 17, 2026
149 of 151 checks passed
@harshach
harshach deleted the harshach/fix-rdf-dedicated-mapper-fields branch September 17, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend 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.

RDF indexing drops tableConstraints, profile, pipelineStatus and usageSummary, starving their dedicated mappers

1 participant