Skip to content

Fix #26834: Patch is failing for nested fields in Topic in main branch#26835

Open
harshach wants to merge 5 commits intomainfrom
topic_patch
Open

Fix #26834: Patch is failing for nested fields in Topic in main branch#26835
harshach wants to merge 5 commits intomainfrom
topic_patch

Conversation

@harshach
Copy link
Copy Markdown
Collaborator

@harshach harshach commented Mar 28, 2026

Describe your changes:

Fixes

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • Fixed patch failures:
    • Corrected field name generation for nested fields in Topic.messageSchema during PATCH operations
    • Fixed similar issues in Container.dataModel.columns, SearchIndex.fields, and APIEndpoint schemas
  • Added test coverage:
    • New ShouldCompareFieldNamesTest validates field name patterns against patch matching logic
    • Integration tests for Topic schema field description, tags, and glossary term updates
    • Playwright tests for Container nested column updates

This will update automatically on new commits.

@harshach harshach requested a review from a team as a code owner March 28, 2026 19:00
Copilot AI review requested due to automatic review settings March 28, 2026 19:00
@github-actions github-actions bot added backend safe to test Add this label to run secure Github workflows on PRs labels Mar 28, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes nested-field PATCH updates being skipped due to mismatched patchedFields vs. recordChange field names for entities whose nested fields are wrapped (e.g., Topic messageSchema.schemaFields, Container dataModel.columns), and adds tests to prevent regressions.

Changes:

  • Update backend updaters (Topic/APIEndpoint/SearchIndex/Columns) to build recordChange field paths using the passed wrapper-prefixed fieldName (via EntityUtil.getFieldName(...) prefixes).
  • Add backend tests: a unit test validating shouldCompare matching behavior for wrapper-prefixed field paths, and integration tests that patch Topic schema-field description/tags/glossary term.
  • Extend Playwright nested-column update test data to include Container with a nested struct column.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/playwright/utils/nestedColumnUpdatesUtils.ts Adds Container support for nested-column row key extraction in Playwright utils
openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts Adds nested struct column test data for Container schema
openmetadata-ui/src/main/resources/ui/playwright/constant/nestedColumnUpdates.ts Registers Container entity for nested-children Playwright scenarios
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java Fixes schema field recordChange paths to include wrapper prefix for PATCH matching
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/APIEndpointRepository.java Same as Topic, for request/response schema fields
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchIndexRepository.java Same as Topic, for nested search index fields
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java Fixes column recordChange paths to include the provided wrapper prefix (e.g., dataModel.columns)
openmetadata-service/src/test/java/org/openmetadata/service/jdbi3/ShouldCompareFieldNamesTest.java Adds unit coverage ensuring wrapper-prefixed field paths match shouldCompare logic
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TopicResourceIT.java Adds integration coverage for patching Topic schema-field description and tags

Comment on lines +1 to +3
package org.openmetadata.service.jdbi3;

import static org.junit.jupiter.api.Assertions.assertTrue;
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

Missing standard Apache 2.0 license header at the top of this new Java test file. Other tests in this module include the header and builds typically enforce it (e.g., via license/checkstyle tooling). Add the repository’s standard license block before the package declaration.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 28, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 64%
65% (58732/90354) 44.75% (30898/69045) 47.89% (9318/19455)

Copilot AI review requested due to automatic review settings March 28, 2026 20:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

updateColumnDataLength(stored, updated);
updateColumnPrecision(stored, updated);
updateColumnScale(stored, updated);
String columnPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

columnPrefix is built via EntityUtil.getFieldName(fieldName, updated.getName()), which does not quote names containing .. Previously EntityUtil.getColumnField(...) used FullyQualifiedName.build(...) and would generate keys like columns."a.b".description, avoiding ambiguous changeDescription paths. Please quote the column name segment (e.g., with FullyQualifiedName.quoteName(updated.getName())) when building the prefix so dotted column names are tracked correctly and PATCH field matching remains consistent.

Suggested change
String columnPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
String columnPrefix =
EntityUtil.getFieldName(fieldName, FullyQualifiedName.quoteName(updated.getName()));

Copilot uses AI. Check for mistakes.
updateFieldDescription(stored, updated);
updateFieldDataTypeDisplay(stored, updated);
updateFieldDisplayName(stored, updated);
String schemaFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

schemaFieldPrefix is constructed with EntityUtil.getFieldName(fieldName, updated.getName()), which won’t quote schema field names containing .. Previously EntityUtil.getSchemaField(...) used FullyQualifiedName.build(...) and would preserve dotted names as a single segment (e.g., messageSchema.schemaFields."a.b".description). Please quote updated.getName() (e.g., FullyQualifiedName.quoteName(...)) when building the prefix to avoid ambiguous changeDescription paths for dotted field names.

Suggested change
String schemaFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
String schemaFieldPrefix =
EntityUtil.getFieldName(fieldName, FullyQualifiedName.quoteName(updated.getName()));

Copilot uses AI. Check for mistakes.
updateFieldDescription(stored, updated);
updateFieldDataTypeDisplay(stored, updated);
updateFieldDisplayName(stored, updated);
String schemaFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

schemaFieldPrefix is built using EntityUtil.getFieldName(fieldName, updated.getName()), which doesn’t quote names containing .. Since schema field names (especially for API payloads) can include dots, this can produce ambiguous changeDescription keys like responseSchema.schemaFields.user.email.description instead of ..."user.email".... Please quote updated.getName() when building the prefix (e.g., via FullyQualifiedName.quoteName(...)).

Suggested change
String schemaFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
String schemaFieldPrefix =
EntityUtil.getFieldName(fieldName, FullyQualifiedName.quoteName(updated.getName()));

Copilot uses AI. Check for mistakes.
updateFieldDescription(stored, updated);
updateFieldDataTypeDisplay(stored, updated);
updateFieldDisplayName(stored, updated);
String searchFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

searchFieldPrefix is built via EntityUtil.getFieldName(fieldName, updated.getName()), which does not quote names containing .. Search index field names commonly include dots (e.g. user.email), and previously EntityUtil.getSearchIndexField(...) used FullyQualifiedName.build(...) to keep such names as a single segment. Please quote updated.getName() when building the prefix (e.g., FullyQualifiedName.quoteName(...)) so changeDescription keys remain unambiguous.

Suggested change
String searchFieldPrefix = EntityUtil.getFieldName(fieldName, updated.getName());
String searchFieldPrefix =
EntityUtil.getFieldName(fieldName, FullyQualifiedName.quoteName(updated.getName()));

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +19
* Tests that field names generated by entity updater helper methods (getFieldName, getSchemaField,
* getColumnField, getSearchIndexField) are compatible with the shouldCompare patchedFields matching
* logic.
*
* <p>This test exists because PR #25751 introduced shouldCompare optimization that skips field
* comparisons when the field name doesn't match the patchedFields extracted from the JSON patch
* path. Entities with wrapper objects (e.g., Topic's messageSchema wrapping schemaFields,
* Container's dataModel wrapping columns) are vulnerable to mismatches when helper methods hardcode
* the inner field name without the wrapper prefix.
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The class Javadoc says the test validates field names generated by multiple helper methods (getSchemaField, getColumnField, getSearchIndexField), but the assertions currently only use EntityUtil.getFieldName(...). Consider either updating the Javadoc to match what’s actually being tested or adding coverage that exercises the other helper methods as well.

Suggested change
* Tests that field names generated by entity updater helper methods (getFieldName, getSchemaField,
* getColumnField, getSearchIndexField) are compatible with the shouldCompare patchedFields matching
* logic.
*
* <p>This test exists because PR #25751 introduced shouldCompare optimization that skips field
* comparisons when the field name doesn't match the patchedFields extracted from the JSON patch
* path. Entities with wrapper objects (e.g., Topic's messageSchema wrapping schemaFields,
* Container's dataModel wrapping columns) are vulnerable to mismatches when helper methods hardcode
* the inner field name without the wrapper prefix.
* Tests that field names generated by {@link EntityUtil#getFieldName(String...)} are compatible
* with the {@code shouldCompare} patchedFields matching logic.
*
* <p>This test exists because PR #25751 introduced a {@code shouldCompare} optimization that
* skips field comparisons when the field name doesn't match the patchedFields extracted from the
* JSON patch path. Entities with wrapper objects (e.g., Topic's messageSchema wrapping
* schemaFields, Container's dataModel wrapping columns) are vulnerable to mismatches when helper
* methods hardcode the fully qualified field path.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 28, 2026

🟡 Playwright Results — all passed (12 flaky)

✅ 2869 passed · ❌ 0 failed · 🟡 12 flaky · ⏭️ 175 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 453 0 2 2
🟡 Shard 2 607 0 1 32
🟡 Shard 3 612 0 2 27
🟡 Shard 4 611 0 6 47
🟡 Shard 5 586 0 1 67
🟡 12 flaky test(s) (passed on retry)
  • Features/CustomizeDetailPage.spec.ts › Dashboard Data Model - customization should work (shard 1, 1 retry)
  • Pages/UserCreationWithPersona.spec.ts › Create user with persona and verify on profile (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/Permissions/GlossaryPermissions.spec.ts › Team-based permissions work correctly (shard 3, 1 retry)
  • Flow/ExploreDiscovery.spec.ts › Should display deleted assets when showDeleted is checked and deleted is not present in queryFilter (shard 3, 1 retry)
  • Pages/Customproperties-part2.spec.ts › entityReferenceList shows item count, scrollable list, no expand toggle (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Description Rule Is_Set (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Domain announcement create, edit & delete (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Multiple consecutive domain renames preserve all associations (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Tag Add, Update and Remove (shard 4, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should allow Data Steward to edit tier for table (shard 5, 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

Copilot AI review requested due to automatic review settings March 29, 2026 03:44
@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Mar 29, 2026

Code Review 👍 Approved with suggestions 1 resolved / 2 findings

Fixes nested fields patching for Topic entities by correcting the updateFieldDisplayName guard condition. Consider optimizing getCertificationClassification() to avoid repeated SettingsCache reads per entity.

💡 Performance: getCertificationClassification called repeatedly per entity

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4589-4598 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4494-4496 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4717 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6765

The method getCertificationClassification() reads from SettingsCache on every call. It is invoked multiple times during entity operations: in getCertification(), applyCertification(), deleteCertificationTag(), getEntitySpecificTags(), and in the EntityUpdater.updateTags() method. For batch operations or list reads, this results in many redundant cache lookups per entity. While SettingsCache is likely fast, the repeated pattern suggests the classification name should be cached once per operation or per repository instance.

✅ 1 resolved
Bug: updateFieldDisplayName guards on getDescription() instead of getDisplayName()

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java:792 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/APIEndpointRepository.java:786 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchIndexRepository.java:649
The updateFieldDisplayName methods in TopicRepository, APIEndpointRepository, and SearchIndexRepository all check !nullOrEmpty(origField.getDescription()) in their bot-guard condition. This should be !nullOrEmpty(origField.getDisplayName()), matching the correct pattern in ColumnEntityUpdater.updateColumnDisplayName (EntityRepository.java:7903).

Impact: When a bot performs a PUT operation, the guard incorrectly uses the description field to decide whether to preserve the original displayName. If description is empty but displayName is set, bot PUT operations will overwrite the existing displayName. Conversely, if description is set but displayName is empty, the bot will incorrectly skip the displayName update.

This is a pre-existing bug but is present in lines modified by this PR (the method signatures were changed to accept a fieldPrefix parameter).

🤖 Prompt for agents
Code Review: Fixes nested fields patching for Topic entities by correcting the updateFieldDisplayName guard condition. Consider optimizing getCertificationClassification() to avoid repeated SettingsCache reads per entity.

1. 💡 Performance: getCertificationClassification called repeatedly per entity
   Files: openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4589-4598, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4494-4496, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:4717, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6765

   The method `getCertificationClassification()` reads from `SettingsCache` on every call. It is invoked multiple times during entity operations: in `getCertification()`, `applyCertification()`, `deleteCertificationTag()`, `getEntitySpecificTags()`, and in the `EntityUpdater.updateTags()` method. For batch operations or list reads, this results in many redundant cache lookups per entity. While `SettingsCache` is likely fast, the repeated pattern suggests the classification name should be cached once per operation or per repository instance.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines +222 to 225
Request request =
new Request(
"GET", String.format("/%s/_doc/%s?_source_includes=%s", TABLE_INDEX, entityId, field));

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

getFieldFromDoc still hardcodes TABLE_INDEX when fetching the document, but the test now resolves entityIndexName dynamically (and uses it for updateEntityEmbedding). This can cause the test to read from a different index than the one being updated (e.g., when cluster alias changes index names). Pass the resolved index name into getFieldFromDoc and use it in the request path instead of TABLE_INDEX.

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +101
// Generate initial embedding synchronously — no polling needed
vectorService.updateEntityEmbedding(table, entityIndexName);

String initialFingerprint = getFieldFromIndex(searchClient, tableId, "fingerprint");
assertNotNull(initialFingerprint, "Initial fingerprint should exist");
String initialFingerprint = getFieldFromDoc(searchClient, tableId, "fingerprint");
assertNotNull(initialFingerprint, "Initial fingerprint should exist after sync embedding");

// Patch description and re-generate embedding synchronously
table.setDescription("Revenue metrics for quarterly financial reporting analysis");
client.tables().update(tableId, table);

Awaitility.await("Wait for embedding update after PATCH")
.atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(2))
.ignoreExceptions()
.until(
() -> {
String fp = getFieldFromIndex(searchClient, tableId, "fingerprint");
return fp != null && !fp.equals(initialFingerprint);
});

String textToEmbed = getFieldFromIndex(searchClient, tableId, "textToEmbed");
Table updated = client.tables().update(tableId, table);
vectorService.updateEntityEmbedding(updated, entityIndexName);

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This test removed the Awaitility wait for the table search document to exist and now calls vectorService.updateEntityEmbedding(...) immediately after client.tables().create(...). The underlying OpenSearch implementation performs a _update without doc_as_upsert, which will fail if the entity search document hasn’t been indexed yet. This makes the test prone to flakiness/failures on slower indexing. Please restore a readiness wait (e.g., wait until the doc exists in the entity index) or change the embedding update path to upsert when the doc is missing.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

@sonarqubecloud
Copy link
Copy Markdown

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.

2 participants