-
Notifications
You must be signed in to change notification settings - Fork 567
Add silent meta update #5144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add silent meta update #5144
Conversation
src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs
Fixed
Show fixed
Hide fixed
|
Are any of these meta fields searchable? |
|
Yes, tags are a default search parameter in meta and other custom search parameters can be created. |
|
@LTA-Thinking, @brendankowitz |
There was a problem hiding this 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 introduces the _silent-meta query parameter for PUT update operations, allowing metadata-only changes to occur without incrementing version numbers or updating the lastUpdated timestamp. This addresses scenarios where frequent metadata updates would otherwise cause unnecessary version history bloat.
Key changes:
- Added
_silent-metaquery parameter that prevents version changes when only metadata fields are modified - Implemented deep comparison logic to detect metadata-only changes
- Added end-to-end tests covering various scenarios of the silent metadata update feature
Reviewed Changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Health.Fhir.Core/Features/KnownQueryParameterNames.cs | Adds the SilentMeta constant for the new query parameter |
| src/Microsoft.Health.Fhir.Core/Messages/Upsert/UpsertResourceRequest.cs | Adds MetaSilent property to track the silent metadata flag |
| src/Microsoft.Health.Fhir.Core/Features/Persistence/ResourceWrapperOperation.cs | Adds MetaSilent property to resource operation wrapper |
| src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Upsert/UpsertResourceHandler.cs | Passes the MetaSilent flag through to the data store operation |
| src/Microsoft.Health.Fhir.Shared.Api/Controllers/FhirController.cs | Adds metaSilent parameter to the Update endpoint |
| src/Microsoft.Health.Fhir.Shared.Client/IFhirClient.cs | Adds silentMeta parameter to update method signatures |
| src/Microsoft.Health.Fhir.Shared.Client/FhirClient.cs | Implements silentMeta parameter handling and URI construction |
| src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs | Implements the core logic to detect metadata-only changes and control history creation |
| src/Microsoft.Health.Fhir.Core/Extensions/ElementValueExtensions.cs | Adds recursive comparison logic for ElementValue instances |
| src/Microsoft.Health.Fhir.Core.UnitTests/Extensions/ElementValueExtensionsTests.cs | Unit tests for the ElementValue comparison logic |
| test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/UpdateTests.cs | E2E tests validating the silent metadata update behavior |
| test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerFhirStorageTestsFixture.cs | Test fixture updates to support the new functionality |
| src/Microsoft.Health.Fhir.SqlServer/Features/Schema/Sql/Sprocs/MergeResources.sql | Documentation comment clarification |
| src/Microsoft.Health.Fhir.Shared.Web/appsettings.Development.json | Changes log level from Debug to Information |
| docs/arch/adr-2510-Silent-meta.md | Architecture decision record documenting the design |
| provenanceHeader: null, | ||
| silentMeta: true); | ||
|
|
||
| // Assert: Historical record should not be created (version and lastUpdated remain change) |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'change' to 'unchanged'.
| // Assert: Historical record should not be created (version and lastUpdated remain change) | |
| // Assert: Historical record should not be created (version and lastUpdated remain unchanged) |
|
|
||
| if (inputMeta.Equals(existingMeta)) | ||
| { | ||
| return false; // Difference is in a non-meta field |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic is inverted. When inputMeta.Equals(existingMeta) returns true, it means the metadata is the same, so changes must be in non-meta fields. However, the method should return true (indicating metadata-only changes) only when the metadata differs AND everything else is the same. This condition should return true to continue checking other fields, not false.
| return false; // Difference is in a non-meta field | |
| return false; // No metadata changes; any difference must be in non-meta fields |
| return false; | ||
| } | ||
|
|
||
| // The end of the file has been reached |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says 'end of the file' but should say 'end of the resource' or 'end of the children', as this is iterating through resource children, not file content.
| // The end of the file has been reached | |
| // The end of the resource's children has been reached |
| using System.Text; | ||
| using System.Threading.Tasks; |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused imports: System.Collections.Generic, System.Linq, System.Text, and System.Threading.Tasks are not used in this file.
| using System.Text; | |
| using System.Threading.Tasks; |
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused imports: System, System.Text, and System.Threading.Tasks are not used in this file.
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| using System.Collections.Generic; | |
| using System.Linq; |
| if (serviceType == typeof(ResourceDeserializer)) | ||
| { | ||
| return ResourceDeserializer as ResourceDeserializer; |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cast is redundant and will always be null. ResourceDeserializer is of type IResourceDeserializer (from line 241), but this is casting it to the concrete type ResourceDeserializer. This should either check for typeof(IResourceDeserializer) or cast to IResourceDeserializer.
| if (serviceType == typeof(ResourceDeserializer)) | |
| { | |
| return ResourceDeserializer as ResourceDeserializer; | |
| if (serviceType == typeof(IResourceDeserializer)) | |
| { | |
| return ResourceDeserializer; |
| } | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.Health": "Information", |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] This appears to be an unrelated configuration change in a development settings file. Changes to logging levels should typically be in a separate PR or should be documented in the PR description if intentional.
| "Microsoft.Health": "Information", |
src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use SilentMetaUpdate instead of SilentMeta
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
I changed the name to Meta History as the only thing it now does is decide to store or not store a historical version. |
src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs
Outdated
Show resolved
Hide resolved
brendankowitz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review: PR #5144 - Add Silent Meta Update
Overview
This PR adds a _meta-history query parameter for PUT requests that allows metadata-only changes to be made without creating a historical version of the resource. This is an optimization to reduce database bloat when only metadata (like tags) is updated.
Critical Issue - Potential PATCH Regression and Data Loss
HIGH SEVERITY: Default Value Mismatch Causes History Loss for All PATCH Operations
Location: UpsertResourceRequest.cs:18
public UpsertResourceRequest(ResourceElement resource, BundleResourceContext bundleResourceContext = null,
WeakETag weakETag = null, bool metaHistory = false) // ⚠️ Default is FALSELocation: PatchResourceHandler.cs:82
return await _mediator.Send<UpsertResourceResponse>(
new UpsertResourceRequest(patchedResource, request.BundleResourceContext, request.WeakETag),
cancellationToken); // ⚠️ metaHistory not passed, uses default FALSEThe Problem:
UpsertResourceRequestconstructor defaultsmetaHistory = falsePatchResourceHandlerdoesn't pass themetaHistoryparameter- When
metaHistory = falseANDChangesAreOnlyInMetadata()returnstrue, NO HISTORY IS KEPT
Location: SqlServerFhirDataStore.cs:304-307
else if (!resourceExt.MetaHistory && ChangesAreOnlyInMetadata(resource, existingResource))
{
metaHistory = false; // Prevents history creation!
}Location: SqlServerFhirDataStore.cs:350
mergeWrappersWithVersions.Add((new MergeResourceWrapper(resource, resourceExt.KeepHistory && metaHistory, ...Impact Scenario:
Any PATCH operation that only modifies metadata (tags, security labels, etc.) will silently skip creating a historical version, even though:
- The user never requested this behavior
- PATCH should always create history (standard FHIR behavior)
- The ADR explicitly states: "This is planned to be added to PATCH...in the future" - meaning it was NOT intended for PATCH yet
Inconsistency: ResourceWrapperOperation vs UpsertResourceRequest Defaults
| Class | metaHistory Default |
|---|---|
ResourceWrapperOperation |
true (safe) |
UpsertResourceRequest |
false (dangerous) |
This inconsistency is confusing and error-prone.
Additional Issues
2. Fragile JSON String Parsing
Location: StringExtensions.cs:154-207 (GetJsonSection)
public static string GetJsonSection(this string input, int startingIndex)This method parses JSON by tracking brackets and quotes manually. Risks:
- Unicode escapes not handled (
\u0022for quote) - Edge cases with nested objects containing
"meta":in string values could cause incorrect parsing - Returns empty string for unmatched brackets instead of throwing (silent failures)
Location: SqlServerFhirDataStore.cs:974
var inputMeta = inputData.GetJsonSection(inputMetaStartIndex);
inputDataWithoutMeta = inputData.Replace(inputMeta, string.Empty, StringComparison.Ordinal);Replacing the meta section content but leaving "meta":{} structure could cause comparison mismatches.
3. Inconsistent Default in FhirController vs UpsertResourceRequest
Location: FhirController.cs:228
[FromQuery(Name = KnownQueryParameterNames.MetaHistory)] bool metaHistory = trueThe controller defaults to true, but:
UpsertResourceRequestdefaults tofalse- This means explicitly calling the constructor without the parameter behaves opposite to the HTTP API
4. Unused Import
Location: StringExtensions.cs:9
using Hl7.FhirPath.Sprache; // Not used in fileRecommendations
Critical Fix Required:
- Change
UpsertResourceRequest.metaHistorydefault totrueto match safe behavior andResourceWrapperOperation - Update
PatchResourceHandlerto explicitly passmetaHistory: trueto ensure PATCH always creates history
Suggested Fix:
// UpsertResourceRequest.cs
public UpsertResourceRequest(ResourceElement resource, BundleResourceContext bundleResourceContext = null,
WeakETag weakETag = null, bool metaHistory = true) // Change to TRUE// PatchResourceHandler.cs - Be explicit
return await _mediator.Send<UpsertResourceResponse>(
new UpsertResourceRequest(patchedResource, request.BundleResourceContext, request.WeakETag, metaHistory: true),
cancellationToken);Summary
| Category | Rating |
|---|---|
| Code Correctness | ❌ Critical bug - PATCH silently loses history |
| Project Conventions | |
| Performance | ✅ Good - Reduces unnecessary DB writes |
| Test Coverage | |
| Security |
Verdict: This PR contains a critical regression where PATCH operations modifying only metadata will silently lose historical versions. This should be patched immediately.
🤖 Generated with Claude Code
Description
Adds a query parameter for PUT updates that allows changes that only update metadata fields to happen without adding a historical version.
The parameter is
_meta-historyex:
PUT <fhir base>/Patient/12456?_meta-history=falseRelated issues
Addresses User Story 167335
Testing
Manual testing, new and existing unit and e2e tests
FHIR Team Checklist
Semver Change (docs)
Patch