Skip to content

Conversation

@fhibf
Copy link
Contributor

@fhibf fhibf commented Sep 16, 2025

Description

Describe the changes in this PR.

Related issues

Addresses AB#170732

Testing

Describe how this change was tested.

FHIR Team Checklist

  • Update the title of the PR to be succinct and less than 65 characters
  • Add a milestone to the PR for the sprint that it is merged (i.e. add S47)
  • Tag the PR with the type of update: Bug, Build, Dependencies, Enhancement, New-Feature or Documentation
  • Tag the PR with Open source, Azure API for FHIR (CosmosDB or common code) or Azure Healthcare APIs (SQL or common code) to specify where this change is intended to be released.
  • Tag the PR with Schema Version backward compatible or Schema Version backward incompatible or Schema Version unchanged if this adds or updates Sql script which is/is not backward compatible with the code.
  • When changing or adding behavior, if your code modifies the system design or changes design assumptions, please create and include an ADR.
  • CI is green before merge Build Status
  • Review squash-merge requirements

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

@fhibf fhibf requested a review from a team as a code owner September 16, 2025 22:57
Undo changes on Search Results.
@fhibf fhibf added the Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs label Sep 19, 2025
@fhibf fhibf added this to the CY25Q3/2Wk12 milestone Sep 19, 2025
@fhibf fhibf added the Enhancement-Optimization Optimization on existing functionality. label Sep 19, 2025
Adding new logic to handle exceptions, not blocking query execution.
Comment on lines 293 to 289
stopwatch.Stop();

if (!string.IsNullOrEmpty(hash))
{
_queryPlanSelector.ReportExecutionTime(hash, reuseQueryCachingPlan, stopwatch.Elapsed.TotalMilliseconds);

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI about 2 months ago

The best way to fix this issue is to replace the generic catch (Exception exception) clause with one or more specific catch clauses that match the expected exception types thrown by the operations in the try block. Examine the methods used here:

  • sqlSearchOptions.Expression.GetHashedUniqueExpressionIdentifier()
  • _queryPlanSelector.GetRecommendedQueryPlanCacheSetting(hash)

Assuming these methods are unlikely to throw anything but programming-related or argument exceptions, the most precise approach is to catch exceptions such as ArgumentException, InvalidOperationException, and potentially NullReferenceException or any custom exception the application expects from those functions. In case the expected exceptions are not documented, it's safest (in absence of other project code) to catch ArgumentException and InvalidOperationException.

Only lines 276-280 should be updated. No imports or additional dependencies are required for these exception types.


Suggested changeset 1
src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
--- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
+++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
@@ -273,11 +273,16 @@
                         _logger.LogInformation("Got Query Plan Caching Setting {ReuseQueryCachingPlan} for hash {Hash}.", reuseQueryCachingPlan, hash);
                     }
                 }
-                catch (Exception exception)
+                catch (ArgumentException exception)
                 {
                     // Swallow any exceptions and just run the query with the default setting.
                     _logger.LogWarning(exception, "Failed to get Query Plan Caching Setting, running with default.");
                 }
+                catch (InvalidOperationException exception)
+                {
+                    // Swallow any exceptions and just run the query with the default setting.
+                    _logger.LogWarning(exception, "Failed to get Query Plan Caching Setting, running with default.");
+                }
 
                 Stopwatch stopwatch = Stopwatch.StartNew();
                 SearchResult searchResult = await SearchImpl(sqlSearchOptions, reuseQueryCachingPlan, cancellationToken);
EOF
@@ -273,11 +273,16 @@
_logger.LogInformation("Got Query Plan Caching Setting {ReuseQueryCachingPlan} for hash {Hash}.", reuseQueryCachingPlan, hash);
}
}
catch (Exception exception)
catch (ArgumentException exception)
{
// Swallow any exceptions and just run the query with the default setting.
_logger.LogWarning(exception, "Failed to get Query Plan Caching Setting, running with default.");
}
catch (InvalidOperationException exception)
{
// Swallow any exceptions and just run the query with the default setting.
_logger.LogWarning(exception, "Failed to get Query Plan Caching Setting, running with default.");
}

Stopwatch stopwatch = Stopwatch.StartNew();
SearchResult searchResult = await SearchImpl(sqlSearchOptions, reuseQueryCachingPlan, cancellationToken);
Copilot is powered by AI and may make mistakes. Always verify output.
…elector.

Adding more validations validating the generation of hashes.
@fhibf fhibf changed the title [Hack] EWMA SQL Query Plan Section [Performance] EWMA SQL Query Plan Section Sep 29, 2025
@LTA-Thinking
Copy link
Contributor

When the times comes the new files need to be moved to a folder with a proper name.

.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.True(scores.ContainsKey(metric));

Check notice

Code scanning / CodeQL

Inefficient use of ContainsKey Note

Inefficient use of 'ContainsKey' and
indexer
.

Copilot Autofix

AI about 2 months ago

To fix the issue, replace the two separate operations: first checking presence of the key with scores.ContainsKey(metric) (line 34) and then accessing its value with scores[metric] (line 35), with a single call to scores.TryGetValue(metric, out var value). This allows both the test for key presence and for the value to be handled efficiently and more idiomatically. Specifically, within the test, use Assert.True(scores.TryGetValue(metric, out var value)); instead of Assert.True(scores.ContainsKey(metric)); and then check Assert.Null(value); instead of reading scores[metric] again. No new methods or imports are required, only a code change within the relevant lines of EwmaTests.cs.


Suggested changeset 1
src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
+++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
@@ -31,8 +31,8 @@
                     .GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                     .GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;
 
-                Assert.True(scores.ContainsKey(metric));
-                Assert.Null(scores[metric]);
+                Assert.True(scores.TryGetValue(metric, out var value));
+                Assert.Null(value);
             }
         }
 
EOF
@@ -31,8 +31,8 @@
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.True(scores.ContainsKey(metric));
Assert.Null(scores[metric]);
Assert.True(scores.TryGetValue(metric, out var value));
Assert.Null(value);
}
}

Copilot is powered by AI and may make mistakes. Always verify output.
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.True(scores.ContainsKey(metric));

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
scores
may be null at this access because of
this
assignment.

Copilot Autofix

AI about 2 months ago

To ensure that a NullReferenceException does not occur, the assignment resulting from the reflection call must be guarded by a null check. Specifically, after GetField().GetValue(ewma), the result should be checked before dereferencing. If the result is null, the test should fail, indicating the presence of an unexpected issue (e.g., field name/type mismatch, signature change, etc.). The appropriate fix is to add a check before using scores. If scores is null, an informative assertion should fail the test immediately, helping developers diagnose reflection issues. This check should be applied in every block where similar reflection usage occurs—namely, in all three tests (Ewma_Constructor_InitializesScoresToNull, Ewma_Update_UpdatesScoreCorrectly, and any other present if applicable).


Suggested changeset 1
src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
+++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
@@ -31,6 +31,7 @@
                     .GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                     .GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;
 
+                Assert.NotNull(scores); // Fail test if reflection yields null
                 Assert.True(scores.ContainsKey(metric));
                 Assert.Null(scores[metric]);
             }
@@ -52,6 +53,7 @@
                 .GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                 .GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;
 
+            Assert.NotNull(scores); // Fail test if reflection yields null
             Assert.Equal(15.0, scores["A"]);
         }
 
EOF
@@ -31,6 +31,7 @@
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.NotNull(scores); // Fail test if reflection yields null
Assert.True(scores.ContainsKey(metric));
Assert.Null(scores[metric]);
}
@@ -52,6 +53,7 @@
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.NotNull(scores); // Fail test if reflection yields null
Assert.Equal(15.0, scores["A"]);
}

Copilot is powered by AI and may make mistakes. Always verify output.
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.Equal(15.0, scores["A"]);

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
scores
may be null at this access because of
this
assignment.

Copilot Autofix

AI about 2 months ago

To fix the problem, we need to ensure that the scores variable is not null before dereferencing it; this means adding a check after the reflection/casting code. The single best fix is to add an assertion that scores != null before accessing scores["A"]. This way, if the reflection fails, the test fails with a clear message rather than throwing a NullReferenceException. Apply this change only to the region where scores is accessed without null checks (lines 54-55 in Ewma_Update_UpdatesScoreCorrectly). No new imports or definitions are required; just an extra Assert.NotNull(scores); before the offending dereference.

Suggested changeset 1
src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
+++ b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/QueryPlanCache/EwmaTests.cs
@@ -52,6 +52,7 @@
                 .GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                 .GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;
 
+            Assert.NotNull(scores);
             Assert.Equal(15.0, scores["A"]);
         }
 
EOF
@@ -52,6 +52,7 @@
.GetField("_ewmaScores", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(ewma) as System.Collections.Concurrent.ConcurrentDictionary<string, double?>;

Assert.NotNull(scores);
Assert.Equal(15.0, scores["A"]);
}

Copilot is powered by AI and may make mistakes. Always verify output.
@fhibf
Copy link
Contributor Author

fhibf commented Sep 30, 2025

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

}

_iterations++;
metricName = _ewmaScores
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this return if this is reached before any scores are set? For example if GetBestMetric is called enough times so that _interactions is greater than _minInteractionsForDecision, but Update hasn't been called yet.

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

Labels

Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Enhancement-Optimization Optimization on existing functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants