diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile
index 693a3619d2..f4616bc684 100644
--- a/build/docker/Dockerfile
+++ b/build/docker/Dockerfile
@@ -1,6 +1,6 @@
# --platform tells docker to always use the host platform for the build not the target platform. Runtime container will use target platform.
# Use .NET 10 SDK to support SQL script generation tool, targeting net10.0 in builds
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.302-azurelinux3.0 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.400-azurelinux3.0 AS build
ARG TARGETARCH
ARG FHIR_VERSION
ARG ASSEMBLY_VER
@@ -87,7 +87,7 @@ COPY . .
RUN dotnet publish /repo/src/Microsoft.Health.Fhir.${FHIR_VERSION}.Web/Microsoft.Health.Fhir.${FHIR_VERSION}.Web.csproj -o "/build" --no-restore -p:AssemblyVersion="${ASSEMBLY_VER}" -p:FileVersion="${ASSEMBLY_VER}" -p:Version="${ASSEMBLY_VER}" -f net10.0 -a $TARGETARCH
# Implicitly uses the target platform for the runtime image.
-FROM mcr.microsoft.com/dotnet/aspnet:10.0.10-azurelinux3.0 AS runtime
+FROM mcr.microsoft.com/dotnet/aspnet:10.0.11-azurelinux3.0 AS runtime
ARG FHIR_VERSION
diff --git a/docs/arch/adr-2608-sql-search-parser-remake.md b/docs/arch/adr-2608-sql-search-parser-remake.md
new file mode 100644
index 0000000000..598d454faa
--- /dev/null
+++ b/docs/arch/adr-2608-sql-search-parser-remake.md
@@ -0,0 +1,55 @@
+# ADR-2608: SQL Search Parser Remake
+
+**Status**: Proposed
+**Date**: 2026-08-21
+**Feature**: SqlSearchParser
+
+## Context
+
+The FHIR server's SQL search query generation pipeline relied on an expression tree architecture where incoming search parameters were first parsed into a generic `Expression` tree, then passed through 14+ chained rewriter/visitor passes (compartment rewriting, date equality semantics, flattening, untyped reference resolution, sort rewriting, partition elimination, predicate pushdown, string overflow handling, numeric range rewriting, include seeding, and more). The final `SqlQueryGenerator` visitor then converted the fully-rewritten expression tree into parameterized SQL, optionally cached as a stored procedure via `CustomQueries`.
+
+This approach had several problems:
+
+- **Debuggability**: Tracing how a FHIR search URL became a SQL query required stepping through 14+ visitor passes, each mutating the expression tree in non-obvious ways. Intermediate states were opaque and difficult to inspect.
+- **Complexity**: Each new search feature (chained searches, reverse chains, SMART scopes, compartments) required adding or modifying rewriter passes that interacted with all other passes, creating a combinatorial explosion of edge cases.
+- **Indirection**: The expression tree abstraction was designed to be storage-agnostic, but in practice the SQL Server backend was the only consumer. The abstraction added layers of indirection without practical benefit.
+- **Performance tuning**: The generated SQL was constrained by what the visitor pattern could express. Optimizations like sharing expensive reference CTEs across multiple chain parameters were architecturally difficult to implement.
+
+## Options Considered
+
+1. **Incremental refactoring of the expression tree pipeline** — Simplify existing rewriters and improve logging *(rejected: the fundamental problem is the multi-pass visitor architecture itself; incremental fixes would not address debuggability or the indirection cost)*
+
+2. **Direct SQL generation from query parameters** — Bypass the expression tree entirely and generate CTEs directly from the parsed query parameters using type-specific SQL parsers *(viable)*
+
+3. **Replace expression tree with a SQL-specific IR** — Keep the expression parsing but introduce a SQL-specific intermediate representation before generation *(rejected: still two translation layers when one suffices; the query parameters already carry all needed information)*
+
+## Decision
+
+We chose **direct SQL generation from query parameters** (Option 2). The new `SearchParameterSqlParser` in `SqlSearchParser/` takes `QueryParams` (a dictionary of search parameter names to values) directly from `SearchOptionsFactory` and produces a raw SQL query string composed of CTEs.
+
+The new pipeline flow is:
+
+```
+HTTP Request
+ → SearchOptionsFactory (parses URL into QueryParams dictionary)
+ → SearchParameterSqlParser.ParseMultiple (generates SQL directly)
+ → CTE-based SQL query string
+ → SqlConnection.ExecuteReader
+```
+
+Each search parameter type has a dedicated parser (`DateTimeSqlParser`, `TokenSqlParser`, `ReferenceSqlParser`, `StringSqlParser`, etc.) that knows how to generate the appropriate CTE for its table. Special parsers handle cross-cutting concerns: `ChainedSqlParser` for forward chains, `ReversedChainSqlParser` for reverse chains, `CompartmentSqlParser` for compartment searches, `SmartCompartmentSqlParser` for SMART scopes, and `IncludeSqlParser` for `_include`/`_revinclude`.
+
+Key architectural features of the new approach:
+
+- **Chain grouping**: Multiple chain parameters sharing the same reference lookup are grouped via `ChainSearchGroup`, allowing the expensive reference CTE to be generated once and reused. An intersection CTE enforces AND semantics across grouped chains.
+- **Linear CTE pipeline**: Each parser appends its CTE to a `SqlQueryBuilder`, with `LastCteName` threading results forward. No multi-pass rewriting needed.
+- **Direct SQL control**: Optimizations like sort-aware paging, continuation token handling, and partition elimination are applied inline during generation rather than as separate visitor passes.
+
+## Consequences
+
+- **Debuggability is dramatically improved.** A standalone `SqlSearchDebugger` tool (in `tools/`) can show the mapping from FHIR URL to SQL query without connecting to a database. The single-pass generation makes it straightforward to trace how each parameter contributes to the final query.
+- **New search features are easier to add.** Adding SMART scope support, for example, required writing one new parser class (`SmartCompartmentSqlParser`) and a few lines in `ParseMultiple`, rather than inserting a new rewriter into a 14-pass chain.
+- **Performance optimizations are more natural.** Chain grouping with shared reference CTEs was a direct architectural addition, not a fight against the visitor pattern.
+- **The expression tree pipeline is retained but dormant.** The old `CreateDefaultSearchExpression` method and its rewriters remain in the codebase (commented out) as a fallback reference. Some expression-based validation (e.g., SMART scope type checking in `ExpressionAccessControl`) still operates on expressions built by `SearchOptionsFactory`.
+- **Storage abstraction is reduced.** The new parser is SQL Server-specific by design. If a second storage backend needed the same search semantics, it would need its own query generator rather than reusing the expression tree. In practice, this trade-off is acceptable since the Cosmos DB backend has its own query pipeline already.
+- **The old `SqlQueryGenerator`, all 14+ rewriter classes, and the `CustomQueries` stored procedure cache are no longer exercised.** These can be removed once the new parser is validated in production.
diff --git a/global.json b/global.json
index 764f391fe5..b8e07d7ce0 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.302"
+ "version": "10.0.400"
},
"test": {
"runner": "Microsoft.Testing.Platform"
diff --git a/nuget.config b/nuget.config
index f40187090d..fff45cfde7 100644
--- a/nuget.config
+++ b/nuget.config
@@ -4,7 +4,7 @@
-
+
diff --git a/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionManager.cs b/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionManager.cs
index 4f82219b34..f16bb79a76 100644
--- a/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionManager.cs
+++ b/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionManager.cs
@@ -386,6 +386,11 @@ private async Task LoadSearchParamsFromDataStore(CancellationToken cancellationT
searchOptions.UnsupportedSearchParams = new List>();
searchOptions.Expression = Expression.SearchParameter(SearchParameterInfo.ResourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, KnownResourceTypes.SearchParameter, false));
searchOptions.MaxItemCount = 10;
+
+ searchOptions.QueryParams = new Dictionary>();
+ searchOptions.QueryParams.Add("_count", new List { "10" });
+ searchOptions.QueryParams.Add("_type", new List { KnownResourceTypes.SearchParameter });
+
searchOptions.ResourceVersionTypes = ResourceVersionType.Latest;
if (continuationToken != null)
{
diff --git a/src/Microsoft.Health.Fhir.Core/Features/KnownQueryParameterNames.cs b/src/Microsoft.Health.Fhir.Core/Features/KnownQueryParameterNames.cs
index 1279f2ed64..4de278aeab 100644
--- a/src/Microsoft.Health.Fhir.Core/Features/KnownQueryParameterNames.cs
+++ b/src/Microsoft.Health.Fhir.Core/Features/KnownQueryParameterNames.cs
@@ -148,5 +148,7 @@ public static class KnownQueryParameterNames
public const string ReturnDetails = "_details";
public const string MetaHistory = "_meta-history";
+
+ public const string ReverseChain = "_has";
}
}
diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/SearchOptions.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/SearchOptions.cs
index 06f170e66b..8f95800623 100644
--- a/src/Microsoft.Health.Fhir.Core/Features/Search/SearchOptions.cs
+++ b/src/Microsoft.Health.Fhir.Core/Features/Search/SearchOptions.cs
@@ -57,6 +57,7 @@ internal SearchOptions(SearchOptions other)
IsAsyncOperation = other.IsAsyncOperation;
SkipAppendIntersectionWithPredecessor = other.SkipAppendIntersectionWithPredecessor;
ContainsIterativeInclude = other.ContainsIterativeInclude;
+ QueryParams = new Dictionary>(other.QueryParams);
}
///
@@ -145,7 +146,7 @@ internal set
///
/// Gets the collection of search parameters used for filtering and querying resources.
///
- public IReadOnlyList SearchParameters { get; internal set; } = new List();
+ public IList SearchParameters { get; internal set; } = new List();
///
/// Gets the list of search parameters that were not used in the search.
@@ -187,6 +188,10 @@ internal set
///
public bool SkipAppendIntersectionWithPredecessor { get; set; }
+#pragma warning disable CA2227 // Collection properties should be read only
+ public IDictionary> QueryParams { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
+
///
/// Gets or sets a value indicating whether the search contains iterative includes.
///
diff --git a/src/Microsoft.Health.Fhir.Core/Properties/AssemblyInfo.cs b/src/Microsoft.Health.Fhir.Core/Properties/AssemblyInfo.cs
index 115d142d47..6d2c3ad1f3 100644
--- a/src/Microsoft.Health.Fhir.Core/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.Health.Fhir.Core/Properties/AssemblyInfo.cs
@@ -52,3 +52,4 @@
[assembly: InternalsVisibleTo("Microsoft.Health.Fhir.R4.ResourceParser")]
[assembly: InternalsVisibleTo("Microsoft.Health.Fhir.SqlServer.UnitTests")]
+[assembly: InternalsVisibleTo("SqlSearchDebugger")]
diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs
index e7a0a41f13..ca25565560 100644
--- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs
+++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs
@@ -66,7 +66,7 @@ public async Task FindMatch(ResourceElement coverage, ResourceE
searchOptions.MaxItemCount = 2;
searchOptions.Sort = new List<(SearchParameterInfo, SortOrder)>();
searchOptions.UnsupportedSearchParams = new List>();
- searchOptions.Expression = CreateSearchExpression(coverage, patient);
+ searchOptions.QueryParams = BuildQueryParams(coverage, patient);
SearchResult results = null;
try
@@ -94,43 +94,15 @@ public async Task FindMatch(ResourceElement coverage, ResourceE
return CreatePatientWithIdentity(patient, results);
}
- private ResourceElement CreatePatientWithIdentity(ResourceElement patient, SearchResult results)
+ private Dictionary> BuildQueryParams(ResourceElement coverage, ResourceElement patient)
{
- var searchMatchOnly = results.Results.Where(x => x.SearchEntryMode == ValueSets.SearchEntryMode.Match).ToList();
- if (searchMatchOnly.Count > 1)
- {
- throw new MemberMatchMatchingException(Core.Resources.MemberMatchMultipleMatchesFound);
- }
+ var queryParams = new Dictionary>();
- if (searchMatchOnly.Count == 0)
- {
- throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound);
- }
+ // Resource type filter - search for Patient resources
+ queryParams["_type"] = new List { KnownResourceTypes.Patient };
- var match = searchMatchOnly[0];
- var element = _resourceDeserializer.Deserialize(match.Resource);
- var foundPatient = element.ToPoco();
- var id = foundPatient.Identifier.Where(x => x.Type != null && x.Type.Coding != null && x.Type.Coding.Exists(x => x.Code == "MB")).FirstOrDefault();
- if (id == null)
- {
- throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound);
- }
-
- var resultPatient = patient.ToPoco();
- var resultId = new Identifier(id.System, id.Value);
- resultId.Type = new CodeableConcept("http://terminology.hl7.org/CodeSystem/v2-0203", "UMB", "Member Match");
- resultPatient.Identifier.Add(resultId);
- var result = resultPatient.ToResourceElement();
- return result;
- }
-
- private MultiaryExpression CreateSearchExpression(ResourceElement coverage, ResourceElement patient)
- {
- IReadOnlyCollection coverageValues = _searchIndexer.Extract(coverage);
+ // Add patient search parameters
IReadOnlyCollection patientValues = _searchIndexer.Extract(patient);
- var expressions = new List();
- var reverseChainExpressions = new List();
- expressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, KnownResourceTypes.Patient, false)));
foreach (SearchIndexEntry patientValue in patientValues)
{
if (IgnoreInSearch(patientValue))
@@ -138,15 +110,25 @@ private MultiaryExpression CreateSearchExpression(ResourceElement coverage, Reso
continue;
}
- var modifier = string.Empty;
+ var paramName = patientValue.SearchParameter.Code;
if (patientValue.SearchParameter.Type == ValueSets.SearchParamType.String)
{
- modifier = ":exact";
+ paramName += ":exact";
}
- expressions.Add(_expressionParser.Parse(new[] { KnownResourceTypes.Patient }, patientValue.SearchParameter.Code + modifier, patientValue.Value.ToString()));
+ var value = patientValue.Value.ToString();
+ if (queryParams.TryGetValue(paramName, out var existingValues))
+ {
+ existingValues.Add(value);
+ }
+ else
+ {
+ queryParams[paramName] = new List { value };
+ }
}
+ // Add coverage search parameters as reverse chain (_has:Coverage:beneficiary:=)
+ IReadOnlyCollection coverageValues = _searchIndexer.Extract(coverage);
foreach (var coverageValue in coverageValues)
{
if (IgnoreInSearch(coverageValue))
@@ -160,26 +142,49 @@ private MultiaryExpression CreateSearchExpression(ResourceElement coverage, Reso
modifier = ":exact";
}
- reverseChainExpressions.Add(_expressionParser.Parse(new[] { KnownResourceTypes.Coverage }, coverageValue.SearchParameter.Code + modifier, coverageValue.Value.ToString()));
- }
-
- if (reverseChainExpressions.Count != 0)
- {
- Expression reverseChainedExpression;
- if (reverseChainExpressions.Count == 1)
+ var hasKey = $"_has:Coverage:beneficiary:{coverageValue.SearchParameter.Code}{modifier}";
+ var value = coverageValue.Value.ToString();
+ if (queryParams.TryGetValue(hasKey, out var existingValues))
{
- reverseChainedExpression = reverseChainExpressions[0];
+ existingValues.Add(value);
}
else
{
- reverseChainedExpression = Expression.And(reverseChainExpressions);
+ queryParams[hasKey] = new List { value };
}
+ }
+
+ return queryParams;
+ }
+
+ private ResourceElement CreatePatientWithIdentity(ResourceElement patient, SearchResult results)
+ {
+ var searchMatchOnly = results.Results.Where(x => x.SearchEntryMode == ValueSets.SearchEntryMode.Match).ToList();
+ if (searchMatchOnly.Count > 1)
+ {
+ throw new MemberMatchMatchingException(Core.Resources.MemberMatchMultipleMatchesFound);
+ }
+
+ if (searchMatchOnly.Count == 0)
+ {
+ throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound);
+ }
- ChainedExpression expression = Expression.Chained(new[] { KnownResourceTypes.Coverage }, _coverageBeneficiaryParameter, new[] { KnownResourceTypes.Patient }, true, reverseChainedExpression);
- expressions.Add(expression);
+ var match = searchMatchOnly[0];
+ var element = _resourceDeserializer.Deserialize(match.Resource);
+ var foundPatient = element.ToPoco();
+ var id = foundPatient.Identifier.Where(x => x.Type != null && x.Type.Coding != null && x.Type.Coding.Exists(x => x.Code == "MB")).FirstOrDefault();
+ if (id == null)
+ {
+ throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound);
}
- return Expression.And(expressions);
+ var resultPatient = patient.ToPoco();
+ var resultId = new Identifier(id.System, id.Value);
+ resultId.Type = new CodeableConcept("http://terminology.hl7.org/CodeSystem/v2-0203", "UMB", "Member Match");
+ resultPatient.Identifier.Add(resultId);
+ var result = resultPatient.ToResourceElement();
+ return result;
}
private static bool IgnoreInSearch(SearchIndexEntry searchEntry) =>
diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Search/SearchOptionsFactory.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Search/SearchOptionsFactory.cs
index d35ca615fe..09907ddd3b 100644
--- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Search/SearchOptionsFactory.cs
+++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Search/SearchOptionsFactory.cs
@@ -130,10 +130,24 @@ public SearchOptions Create(
bool setDefaultBundleTotal = true;
var notReferencedSearches = new List();
+ searchOptions.QueryParams = new Dictionary>();
+
// Extract the continuation token, filter out the other known query parameters that's not search related.
// Exclude time travel parameters from evaluation to avoid warnings about unsupported parameters
foreach (Tuple query in queryParameters?.Where(_ => !_queryHintParameterNames.Contains(_.Item1)) ?? Enumerable.Empty>())
{
+ if (!string.IsNullOrEmpty(query.Item1))
+ {
+ if (searchOptions.QueryParams.TryGetValue(query.Item1, out var values))
+ {
+ values.Add(query.Item2);
+ }
+ else
+ {
+ searchOptions.QueryParams[query.Item1] = new List() { query.Item2 };
+ }
+ }
+
if (query.Item1 == KnownQueryParameterNames.ContinuationToken)
{
// This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist.
@@ -391,6 +405,14 @@ public SearchOptions Create(
var resourceTypesString = parsedResourceTypes.Select(x => x.ToString()).ToArray();
+ var singleResourceTypesString = string.Join(",", resourceTypesString);
+ searchOptions.QueryParams.Remove("_type");
+
+ if (!singleResourceTypesString.Equals(KnownResourceTypes.DomainResource, StringComparison.OrdinalIgnoreCase))
+ {
+ searchOptions.QueryParams.Add("_type", new List { singleResourceTypesString });
+ }
+
// Form all the include revinclude expressions before for the Smart queries access control check
// Collect all the resource types required by the include/revinclude expressions
var includeRevincludeSearchExpressions = new List();
@@ -398,6 +420,13 @@ public SearchOptions Create(
includeRevincludeSearchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams.RevInclude, resourceTypesString, true).Where(e => e != null));
var requiredResourceTypes = includeRevincludeSearchExpressions.SelectMany(x => x.Produces).ToList();
+ var invalidRevIncludeParameters = searchParams.RevInclude.Where(x => x.Item2 != IncludeModifier.None && x.Item1.Contains('*', StringComparison.OrdinalIgnoreCase));
+ foreach (var invalidRevInclude in invalidRevIncludeParameters)
+ {
+ var paramName = KnownQueryParameterNames.ReverseInclude + (invalidRevInclude.Item2 != IncludeModifier.None ? ":" + invalidRevInclude.Item2.ToString().ToLowerInvariant() : string.Empty);
+ searchOptions.QueryParams[paramName].Remove(invalidRevInclude.Item1);
+ }
+
// Add the parsed resource types to the required resource types for access control check
// Now it contains all the resource types that are requested by the search,
// including those from the search path, _type parameter, and resource types returned via include/revinclude expressions
@@ -405,6 +434,25 @@ public SearchOptions Create(
CheckFineGrainedAccessControl(searchExpressions, searchParams, requiredResourceTypes);
+ // Add fine-grained access control resource type restrictions to QueryParams for the SQL parser
+ if (_contextAccessor.RequestContext?.AccessControlContext?.ApplyFineGrainedAccessControl == true)
+ {
+ var allowedActions = _contextAccessor.RequestContext?.AccessControlContext?.AllowedResourceActions;
+ if (allowedActions != null && !allowedActions.Any(a => a.Resource == KnownResourceTypes.All))
+ {
+ var allowedTypes = allowedActions.Select(a => a.Resource).Distinct().ToList();
+ if (allowedTypes.Any())
+ {
+ searchOptions.QueryParams["_fhirScopeAllowedTypes"] = allowedTypes;
+ }
+ else
+ {
+ // No resource types allowed — block all queries
+ searchOptions.QueryParams["_fhirScopeAllowedTypes"] = new List { "none" };
+ }
+ }
+ }
+
var validSearchParameters = new List();
// Deduplicate exact (name, value) query parameter pairs before parsing. Repeated identical parameters produce
@@ -430,6 +478,7 @@ public SearchOptions Create(
catch (SearchParameterNotSupportedException)
{
unsupportedSearchParameters.Add(q);
+ searchOptions.QueryParams.Remove(q.Item1);
return null;
}
@@ -468,6 +517,10 @@ public SearchOptions Create(
{
searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId, resourceTypesString));
}
+
+ // Add compartment info to QueryParams so the SQL parser can generate compartment joins
+ searchOptions.QueryParams["_compartmentType"] = new List { compartmentType };
+ searchOptions.QueryParams["_compartmentId"] = new List { compartmentId };
}
else
{
@@ -493,6 +546,10 @@ public SearchOptions Create(
{
searchExpressions.Add(Expression.SmartCompartmentSearch(smartCompartmentType, smartCompartmentId, resourceTypesString));
}
+
+ // Add SMART compartment info to QueryParams so the SQL parser can generate SMART compartment joins
+ searchOptions.QueryParams["_smartCompartmentType"] = new List { smartCompartmentType };
+ searchOptions.QueryParams["_smartCompartmentId"] = new List { smartCompartmentId };
}
else
{
@@ -595,6 +652,11 @@ public SearchOptions Create(
var allErrors = new List();
foreach (Tuple unsupported in unsupportedSearchParameters)
{
+ if (!string.IsNullOrEmpty(unsupported.Item1))
+ {
+ searchOptions.QueryParams.Remove(unsupported.Item1);
+ }
+
allErrors.Add(string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, unsupported.Item1, string.Join(",", resourceTypesString)));
}
@@ -824,7 +886,7 @@ private void CheckFineGrainedAccessControl(List searchExpressions, S
foreach (var param in restriction.SearchParameters.Parameters)
{
- searchParams.Add(param.Item1, param.Item2);
+ searchParams.Add(param.Item1, param.Item2);
}
}
diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Validation/ServerProvideProfileValidation.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Validation/ServerProvideProfileValidation.cs
index 103e2bc5eb..febace834d 100644
--- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Validation/ServerProvideProfileValidation.cs
+++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Validation/ServerProvideProfileValidation.cs
@@ -241,7 +241,11 @@ private async Task> GetSummariesAsync(CancellationToken ca
{
do
{
- var queryParameters = new List>();
+ var queryParameters = new List>()
+ {
+ new Tuple(KnownQueryParameterNames.Type, type),
+ };
+
if (ct != null)
{
ct = ContinuationTokenEncoder.Encode(ct);
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/ChainFlatteningRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/ChainFlatteningRewriterTests.cs
deleted file mode 100644
index 833657dab7..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/ChainFlatteningRewriterTests.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors.QueryGenerators;
-using Microsoft.Health.Fhir.SqlServer.Features.Storage;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- ///
- /// Unit tests for ChainFlatteningRewriter.
- /// These tests verify the rewriter's behavior using non-chain expressions to avoid ModelInfoProvider dependencies.
- /// The ChainFlatteningRewriter is more comprehensively tested through integration tests where
- /// the full FHIR stack (including ModelInfoProvider) is initialized.
- ///
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class ChainFlatteningRewriterTests
- {
- private readonly ChainFlatteningRewriter _rewriter;
-
- public ChainFlatteningRewriterTests()
- {
- var searchParamTypeMap = new SearchParameterToSearchValueTypeMap();
- var queryGeneratorFactory = new SearchParamTableExpressionQueryGeneratorFactory(searchParamTypeMap);
- _rewriter = new ChainFlatteningRewriter(queryGeneratorFactory);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithoutChainExpressions_WhenVisited_ThenSameExpressionIsReturned()
- {
- // Arrange - Expression with no chain expressions
- var normalExpression = Expression.Equals(FieldName.TokenCode, null, "code123");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, normalExpression, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- };
-
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - Should return same expression since there are no chains
- Assert.Same(sqlRootExpression, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenEmptySqlRootExpression_WhenVisited_ThenSameExpressionIsReturned()
- {
- // Arrange
- var tableExpressions = new List();
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - Empty list means no modifications needed
- Assert.Same(sqlRootExpression, result);
- }
-
- [Fact]
- public void GivenSqlRootWithMultipleNonChainExpressions_WhenVisited_ThenAllArePreserved()
- {
- // Arrange
- var expr1 = Expression.Equals(FieldName.TokenCode, null, "code1");
- var expr2 = Expression.Equals(FieldName.String, null, "value2");
- var expr3 = Expression.GreaterThan(FieldName.Number, null, 10);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, expr1, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, expr2, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, expr3, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - All expressions should be preserved as-is
- Assert.Same(sqlRootExpression, result);
- Assert.Equal(3, result.SearchParamTableExpressions.Count);
- Assert.Same(expr1, result.SearchParamTableExpressions[0].Predicate);
- Assert.Same(expr2, result.SearchParamTableExpressions[1].Predicate);
- Assert.Same(expr3, result.SearchParamTableExpressions[2].Predicate);
- }
-
- [Fact]
- public void GivenSqlRootWithTopExpression_WhenVisited_ThenTopIsPreserved()
- {
- // Arrange
- var normalExpr = Expression.Equals(FieldName.TokenCode, null, "test");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, normalExpr, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- };
-
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - Top expression should be unchanged
- Assert.Same(sqlRootExpression, result);
- Assert.Equal(SearchParamTableExpressionKind.Top, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenSqlRootWithResourceTableExpressions_WhenVisited_ThenResourceTableExpressionsArePreserved()
- {
- // Arrange
- var tableExpression = new SearchParamTableExpression(null, Expression.Equals(FieldName.TokenCode, null, "test"), SearchParamTableExpressionKind.Normal);
- var resourceTableExpression = Expression.SearchParameter(new SearchParameterInfo("_type", "_type"), Expression.Equals(FieldName.TokenCode, null, "Patient"));
-
- var sqlRootExpression = new SqlRootExpression(
- new List { tableExpression },
- new List { resourceTableExpression });
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - ResourceTableExpressions should be preserved
- Assert.Same(sqlRootExpression, result);
- Assert.Single(result.ResourceTableExpressions);
- Assert.Same(resourceTableExpression, result.ResourceTableExpressions[0]);
- }
-
- [Fact]
- public void GivenRewriterInstance_WhenCreated_ThenNotNull()
- {
- // Assert - Verify rewriter was created successfully
- Assert.NotNull(_rewriter);
- }
-
- [Fact]
- public void GivenSqlRootWithAllExpression_WhenVisited_ThenAllExpressionPreserved()
- {
- // Arrange
- var allExpression = Expression.Equals(FieldName.TokenCode, null, "all-test");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, allExpression, SearchParamTableExpressionKind.All),
- };
-
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert
- Assert.Same(sqlRootExpression, result);
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
- }
-
- [Fact]
- public void GivenSqlRootWithIncludeExpression_WhenVisited_ThenIncludeExpressionPreserved()
- {
- // Arrange
- var includeExpression = Expression.Equals(FieldName.String, null, "include-test");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRootExpression = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRootExpression.AcceptVisitor(_rewriter, null);
-
- // Assert - Non-chain expressions should pass through unchanged
- Assert.Same(sqlRootExpression, result);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[0].Kind);
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/DateTimeBoundedRangeRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/DateTimeBoundedRangeRewriterTests.cs
deleted file mode 100644
index 4acca26ca6..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/DateTimeBoundedRangeRewriterTests.cs
+++ /dev/null
@@ -1,408 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- ///
- /// Unit tests for DateTimeBoundedRangeRewriter.
- /// Tests the rewriter's optimization of datetime range queries by adding a bounded range check
- /// for dates shorter than one day to improve query performance.
- ///
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class DateTimeBoundedRangeRewriterTests
- {
- private static readonly DateTimeOffset BaseDate = new DateTimeOffset(2024, 1, 15, 12, 0, 0, TimeSpan.Zero);
-
- [Fact]
- public void GivenDateTimeBoundedRange_WhenRewritten_ThenCreatesShortRangeOptimization()
- {
- // Arrange - Pattern: (DateTimeEnd >= X) AND (DateTimeStart < Y)
- var greaterThanExpr = Expression.GreaterThanOrEqual(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert - Should create two table expressions: original + optimization for short ranges
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- // Original expression
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
-
- // Optimized concatenation for short ranges
- var concatenation = result.SearchParamTableExpressions[1];
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, concatenation.Kind);
-
- var concatenationAnd = Assert.IsType(concatenation.Predicate);
- Assert.Equal(MultiaryOperator.And, concatenationAnd.MultiaryOperation);
- Assert.Equal(4, concatenationAnd.Expressions.Count);
-
- // First: DateTimeIsLongerThanADay = false
- var isNotLongExpr = Assert.IsType(concatenationAnd.Expressions[0]);
- Assert.Equal(SqlFieldName.DateTimeIsLongerThanADay, isNotLongExpr.FieldName);
- Assert.Equal(BinaryOperator.Equal, isNotLongExpr.BinaryOperator);
- Assert.Equal(false, isNotLongExpr.Value);
-
- // Second: DateTimeEnd >= X
- var endExpr = Assert.IsType(concatenationAnd.Expressions[1]);
- Assert.Equal(FieldName.DateTimeEnd, endExpr.FieldName);
-
- // Third: DateTimeStart >= (X - 1 day)
- var startBoundedExpr = Assert.IsType(concatenationAnd.Expressions[2]);
- Assert.Equal(FieldName.DateTimeStart, startBoundedExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThanOrEqual, startBoundedExpr.BinaryOperator);
- Assert.Equal(BaseDate.AddTicks(-TimeSpan.TicksPerDay), startBoundedExpr.Value);
-
- // Fourth: DateTimeStart < Y
- var startExpr = Assert.IsType(concatenationAnd.Expressions[3]);
- Assert.Equal(FieldName.DateTimeStart, startExpr.FieldName);
- }
-
- [Fact]
- public void GivenLongDateRange_WhenRewritten_ThenCreatesLongRangeExpression()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThanOrEqual(FieldName.DateTimeStart, null, BaseDate.AddDays(2));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- // Original expression remains
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
-
- // Concatenation for long ranges - The Scout creates And with 3 expressions (DateTimeIsLongerThanADay=true + 2 original)
- // But then VisitMultiary detects this pattern and transforms it to And with 4 expressions (DateTimeIsLongerThanADay=false + optimization)
- var concatenation = result.SearchParamTableExpressions[1];
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, concatenation.Kind);
-
- var concatenationAnd = Assert.IsType(concatenation.Predicate);
- Assert.Equal(4, concatenationAnd.Expressions.Count); // Changed from 3 to 4
-
- // Should have DateTimeIsLongerThanADay = false (not true - gets flipped by VisitMultiary)
- var isLongExpr = Assert.IsType(concatenationAnd.Expressions[0]);
- Assert.Equal(SqlFieldName.DateTimeIsLongerThanADay, isLongExpr.FieldName);
- Assert.Equal(false, isLongExpr.Value); // Changed from true to false
- }
-
- [Fact]
- public void GivenNonDateTimeExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Number expression should not be rewritten
- var expression = Expression.GreaterThan(FieldName.Number, null, 10);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert - Should return unchanged
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenSingleDateTimeExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Single expression (not And) should not be rewritten
- var expression = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenOrExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Or instead of And
- var expr1 = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var expr2 = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var orExpression = Expression.Or(expr1, expr2);
-
- var sqlRoot = CreateSqlRootWithExpression(orExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenWrongFieldNames_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Both fields must be DateTimeEnd and DateTimeStart
- var expr1 = Expression.GreaterThan(FieldName.DateTimeStart, null, BaseDate);
- var expr2 = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(expr1, expr2);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenWrongOperators_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - First must be >= or >, second must be < or <=
- var expr1 = Expression.LessThan(FieldName.DateTimeEnd, null, BaseDate);
- var expr2 = Expression.GreaterThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(expr1, expr2);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenComponentIndex_WhenRewritten_ThenComponentIndexPreserved()
- {
- // Arrange
- var componentIndex = 1;
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, componentIndex, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, componentIndex, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationAnd = Assert.IsType(result.SearchParamTableExpressions[1].Predicate);
-
- // Verify all expressions have the same component index using Select
- var componentIndices = concatenationAnd.Expressions
- .Select(expr => Assert.IsType(expr))
- .Select(binaryExpr => binaryExpr.ComponentIndex)
- .ToList();
-
- Assert.All(componentIndices, idx => Assert.Equal(componentIndex, idx));
- }
-
- [Fact]
- public void GivenThreeExpressions_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - And with 3 expressions (pattern expects exactly 2)
- var expr1 = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var expr2 = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var expr3 = Expression.Equals(FieldName.TokenCode, null, "test");
- var andExpression = Expression.And(expr1, expr2, expr3);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenExpressionsInReverseOrder_WhenRewritten_ThenRewriterHandlesReordering()
- {
- // Arrange - LessThan before GreaterThan (should still match after reordering)
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var andExpression = Expression.And(lessThanExpr, greaterThanExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert - Should still be rewritten (rewriter sorts by operator)
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenEmptySqlRoot_WhenRewritten_ThenReturnsUnchanged()
- {
- // Arrange
- var sqlRoot = new SqlRootExpression(
- new List(),
- new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenChainExpression_WhenRewritten_ThenSkipsChainExpressions()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, andExpression, SearchParamTableExpressionKind.Chain),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert - Chain expressions should be skipped
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenIncludeExpression_WhenRewritten_ThenSkipsIncludeExpressions()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, andExpression, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenSortExpression_WhenRewritten_ThenSkipsSortExpressions()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, andExpression, SearchParamTableExpressionKind.Sort),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenAllExpression_WhenRewritten_ThenSkipsAllExpressions()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanExpr);
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, andExpression, SearchParamTableExpressionKind.All),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenGreaterThanWithLessThanOrEqual_WhenRewritten_ThenCreatesOptimization()
- {
- // Arrange
- var greaterThanExpr = Expression.GreaterThan(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanOrEqualExpr = Expression.LessThanOrEqual(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanExpr, lessThanOrEqualExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert - Should create optimization
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenGreaterThanOrEqualWithLessThan_WhenRewritten_ThenCreatesOptimization()
- {
- // Arrange
- var greaterThanOrEqualExpr = Expression.GreaterThanOrEqual(FieldName.DateTimeEnd, null, BaseDate);
- var lessThanExpr = Expression.LessThan(FieldName.DateTimeStart, null, BaseDate.AddHours(6));
- var andExpression = Expression.And(greaterThanOrEqualExpr, lessThanExpr);
-
- var sqlRoot = CreateSqlRootWithExpression(andExpression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(DateTimeBoundedRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
- }
-
- private static SqlRootExpression CreateSqlRootWithExpression(Expression expression)
- {
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, expression, SearchParamTableExpressionKind.Normal),
- };
-
- return new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs
deleted file mode 100644
index da35abbc56..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/FlatteningRewriterTests.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class FlatteningRewriterTests
- {
- [Fact]
- public void GivenAMultiaryExpressionWithASingleElement_WhenFlattened_RemovesTheMultiary()
- {
- MultiaryExpression inputExpression = Expression.And(Expression.Equals(FieldName.Number, null, 1));
- Expression visitedExpression = inputExpression.AcceptVisitor(FlatteningRewriter.Instance);
- Assert.Equal("(FieldEqual Number 1)", visitedExpression.ToString());
- }
-
- [Fact]
- public void GivenTwoLayersOfAndExpressions_WhenFlattened_CombinesToOneAndExpression()
- {
- MultiaryExpression inputExpression =
- Expression.And(
- Expression.And(Expression.GreaterThan(FieldName.Number, null, 1), Expression.LessThan(FieldName.Number, null, 5)),
- Expression.And(Expression.GreaterThan(FieldName.Quantity, null, 1), Expression.LessThan(FieldName.Quantity, null, 5)));
-
- Expression visitedExpression = inputExpression.AcceptVisitor(FlatteningRewriter.Instance);
- Assert.Equal("(And (FieldGreaterThan Number 1) (FieldLessThan Number 5) (FieldGreaterThan Quantity 1) (FieldLessThan Quantity 5))", visitedExpression.ToString());
- }
-
- [Fact]
- public void GivenTwoLayersOfOrExpressions_WhenFlattened_CombinesToOneOrExpression()
- {
- MultiaryExpression inputExpression =
- Expression.Or(
- Expression.Or(Expression.GreaterThan(FieldName.Number, null, 1), Expression.LessThan(FieldName.Number, null, 5)),
- Expression.Or(Expression.GreaterThan(FieldName.Quantity, null, 1), Expression.LessThan(FieldName.Quantity, null, 5)));
-
- Expression visitedExpression = inputExpression.AcceptVisitor(FlatteningRewriter.Instance);
- Assert.Equal("(Or (FieldGreaterThan Number 1) (FieldLessThan Number 5) (FieldGreaterThan Quantity 1) (FieldLessThan Quantity 5))", visitedExpression.ToString());
- }
-
- [Fact]
- public void GivenAnOrExpressionWithAnAndChild_WhenFlattened_RemainsTheSame()
- {
- MultiaryExpression inputExpression =
- Expression.Or(
- Expression.And(Expression.GreaterThan(FieldName.Number, null, 1), Expression.LessThan(FieldName.Number, null, 5)),
- Expression.And(Expression.GreaterThan(FieldName.Quantity, null, 1), Expression.LessThan(FieldName.Quantity, null, 5)));
-
- Expression visitedExpression = inputExpression.AcceptVisitor(FlatteningRewriter.Instance);
- Assert.Equal("(Or (And (FieldGreaterThan Number 1) (FieldLessThan Number 5)) (And (FieldGreaterThan Quantity 1) (FieldLessThan Quantity 5)))", visitedExpression.ToString());
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeMatchSeedRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeMatchSeedRewriterTests.cs
deleted file mode 100644
index 50a4aa8a84..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeMatchSeedRewriterTests.cs
+++ /dev/null
@@ -1,431 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- ///
- /// Unit tests for IncludeMatchSeedRewriter.
- /// Tests the rewriter's ability to add an All SearchParamTableExpression as a seed for match results
- /// when SearchParamTableExpressions consist solely of Include expressions.
- ///
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class IncludeMatchSeedRewriterTests
- {
- [Fact]
- public void GivenOnlyIncludeExpressions_WhenRewritten_ThenAllExpressionIsAdded()
- {
- // Arrange - Query like: Observation?_include=Observation:subject
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceTableExpression = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceTableExpression });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Should add an All expression at the beginning
- Assert.NotNull(result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- // First expression should be All (seed)
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
-
- // Second should be the original Include
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Same(includeExpression, result.SearchParamTableExpressions[1].Predicate);
-
- // ResourceTableExpressions should be cleared and moved to All expression
- Assert.Empty(result.ResourceTableExpressions);
- }
-
- [Fact]
- public void GivenMultipleIncludeExpressions_WhenRewritten_ThenAllExpressionAddedBeforeAll()
- {
- // Arrange - Multiple includes with a resource expression to avoid validation error
- var include1 = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var include2 = Expression.Equals(FieldName.ReferenceResourceType, null, "Practitioner");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, include1, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, include2, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Equal(3, result.SearchParamTableExpressions.Count);
-
- // First should be All
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
-
- // Remaining should be the includes in order
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Same(include1, result.SearchParamTableExpressions[1].Predicate);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[2].Kind);
- Assert.Same(include2, result.SearchParamTableExpressions[2].Predicate);
- }
-
- [Fact]
- public void GivenMixedIncludeAndNormalExpressions_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Mix of Include and Normal (like: Observation?code=abc&_include=Observation:subject)
- var normalExpression = Expression.Equals(FieldName.TokenCode, null, "abc");
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, normalExpression, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Should return unchanged
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenNormalExpressionsOnly_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - No includes
- var expression = Expression.Equals(FieldName.TokenCode, null, "test");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, expression, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenEmptySqlRoot_WhenRewritten_ThenReturnsUnchanged()
- {
- // Arrange
- var sqlRoot = new SqlRootExpression(
- new List(),
- new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenIncludeWithMultipleResourceTableExpressions_WhenRewritten_ThenResourceExpressionsAreCombined()
- {
- // Arrange
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr1 = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var resourceExpr2 = Expression.SearchParameter(
- new SearchParameterInfo("_lastUpdated", "_lastUpdated"),
- Expression.GreaterThan(FieldName.DateTimeStart, null, System.DateTimeOffset.UtcNow));
-
- var resourceTableExpressions = new List { resourceExpr1, resourceExpr2 };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, resourceTableExpressions);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
-
- // The All expression should contain an And combining the resource expressions
- var allPredicate = result.SearchParamTableExpressions[0].Predicate;
- Assert.IsType(allPredicate);
- var andExpression = (MultiaryExpression)allPredicate;
- Assert.Equal(MultiaryOperator.And, andExpression.MultiaryOperation);
- Assert.Equal(2, andExpression.Expressions.Count);
- }
-
- [Fact]
- public void GivenIncludeWithSingleResourceTableExpression_WhenRewritten_ThenResourceExpressionWrappedInAnd()
- {
- // Arrange
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Expression.And() always creates And expression even with single element
- var allPredicate = result.SearchParamTableExpressions[0].Predicate;
- var andExpression = Assert.IsType(allPredicate);
- Assert.Equal(MultiaryOperator.And, andExpression.MultiaryOperation);
- Assert.Single(andExpression.Expressions);
- Assert.Same(resourceExpr, andExpression.Expressions[0]);
- }
-
- [Fact]
- public void GivenIncludeWithChainExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Include with Chain
- var chainExpression = Expression.Equals(FieldName.String, null, "chain-test");
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, chainExpression, SearchParamTableExpressionKind.Chain),
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Mixed types, no rewrite
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenIncludeWithTopExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Mixed types, no rewrite
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenIncludeWithSortExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange
- var sortExpression = Expression.Equals(FieldName.DateTimeStart, null, System.DateTimeOffset.UtcNow);
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, sortExpression, SearchParamTableExpressionKind.Sort),
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenIncludeWithAllExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Already has All expression
- var allExpression = Expression.Equals(FieldName.TokenCode, null, "all-test");
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, allExpression, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert - Mixed types, no rewrite
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenThreeIncludeExpressions_WhenRewritten_ThenAllAddedAndOrderPreserved()
- {
- // Arrange
- var include1 = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var include2 = Expression.Equals(FieldName.ReferenceResourceType, null, "Practitioner");
- var include3 = Expression.Equals(FieldName.ReferenceResourceType, null, "Organization");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, include1, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, include2, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, include3, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[3].Kind);
-
- // Verify order is preserved
- Assert.Same(include1, result.SearchParamTableExpressions[1].Predicate);
- Assert.Same(include2, result.SearchParamTableExpressions[2].Predicate);
- Assert.Same(include3, result.SearchParamTableExpressions[3].Predicate);
- }
-
- [Fact]
- public void GivenIncludeWithConcatenationExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - Include with Concatenation
- var concatenationExpression = Expression.Equals(FieldName.Number, null, 42);
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, concatenationExpression, SearchParamTableExpressionKind.Concatenation),
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Observation"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenOnlyIncludesWithComplexResourceExpression_WhenRewritten_ThenResourceExpressionCombinedInAnd()
- {
- // Arrange
- var includeExpression = Expression.Equals(FieldName.ReferenceResourceType, null, "Patient");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpression, SearchParamTableExpressionKind.Include),
- };
-
- // Complex resource expression with Or
- var typeExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Or(
- Expression.Equals(FieldName.TokenCode, null, "Observation"),
- Expression.Equals(FieldName.TokenCode, null, "Condition")));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { typeExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludeMatchSeedRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.All, result.SearchParamTableExpressions[0].Kind);
-
- // Expression.And() wraps even single expressions
- var allPredicate = result.SearchParamTableExpressions[0].Predicate;
- var andExpression = Assert.IsType(allPredicate);
- Assert.Equal(MultiaryOperator.And, andExpression.MultiaryOperation);
- Assert.Single(andExpression.Expressions);
- Assert.Same(typeExpr, andExpression.Expressions[0]);
- }
-
- [Fact]
- public void GivenRewriterInstance_WhenAccessed_ThenNotNull()
- {
- // Assert - Verify singleton instance exists
- Assert.NotNull(IncludeMatchSeedRewriter.Instance);
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeRewriterTests.cs
deleted file mode 100644
index 4dfb489fc8..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludeRewriterTests.cs
+++ /dev/null
@@ -1,1677 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Medino;
-using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Health.Fhir.Core.Features.Definition;
-using Microsoft.Health.Fhir.Core.Features.Persistence;
-using Microsoft.Health.Fhir.Core.Features.Search;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Features.Search.Parameters;
-using Microsoft.Health.Fhir.Core.Features.Search.Registry;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.Core.UnitTests.Extensions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors.QueryGenerators;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using NSubstitute;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class IncludeRewriterTests : IClassFixture, IAsyncLifetime
- {
- private readonly IncludeRewriterFixture _fixture;
- private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
-
- public IncludeRewriterTests(IncludeRewriterFixture fixture)
- {
- _fixture = fixture;
- _searchParameterDefinitionManager = fixture.SearchParameterDefinitionManager;
- }
-
- public async Task InitializeAsync()
- {
- await _fixture.Start();
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- // Basic Queries with 0-2 include search parameters with all the pair combinations
-
- [Fact]
- public void GivenASqlRootExpressionWithoutIncludes_WhenVisitedByIncludeRewriter_TheSameExpressionShouldBeReturnedAsIs()
- {
- // Leave the query as is if there's no Include expression. For example:
- // [base]/Patient?gender=female&family=Ellison
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Patient", false)),
- new SearchParameterExpression(new SearchParameterInfo("gender", "gender"), new StringExpression(StringOperator.Equals, FieldName.String, null, "female", false)),
- new SearchParameterExpression(new SearchParameterInfo("family", "family"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Ellison", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var rewrittenExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(rewrittenExpressions);
- Assert.Equal(2, rewrittenExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, rewrittenExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, rewrittenExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithTwoIncludes_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include=MedicationDispense:prescription&_include=MedicationDispense:patient&_id=smart-MedicationDispense-567
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "smart-MedicationDispense-567", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneIncludeIterate_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner&_include=MedicationRequest:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneRevInclude_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/MedicationRequest?_include=MedicationRequest:patient&_revinclude=MedicationDispense:prescription&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var revincludeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneRevIncludeIterate_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationRequest?_revinclude:iterate=MedicationDispense:patient&_include=MedicationRequest:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var revincludeIterateMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeIterateMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithTwoRevIncludes_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/Patient?_revinclude=MedicationDispense:patient&_revinclude=MedicationRequest:patient&_id=patientId
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var revincludeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var revincludeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Patient", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "patientId", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithTwoIncludesSpecifyingTargetType_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include=MedicationDispense:prescription&_include=MedicationDispense:subject:Patient&_id=smart-MedicationDispense-567
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "subject");
- var includeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", "Patient", null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "smart-MedicationDispense-567", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("subject", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneIncludeIterateSpecifyingTargetType_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner:Practitioner&_include=MedicationRequest:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", "Practitioner", null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneRevIncludeSpecifyingTargetType_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/MedicationRequest?_include=MedicationRequest:patient&_revinclude=MedicationDispense:prescription:MedicationRequest&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var revincludeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", "MedicationRequest", null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneIncludeAndOneRevIncludeIterateSpecifyingTargetType_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationRequest?_revinclude:iterate=MedicationDispense:patient&_include=MedicationRequest:subject:Patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var revincludeIterateMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "subject");
- var includeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", "Patient", null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeIterateMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("subject", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithTwoRevIncludesSpecifyingTargetType_WhenVisitedByIncludeRewriter_TheOrderDoesNotMatterAndShouldRemainUnchanged()
- {
- // Order the following query:
- // [base]/Patient?_revinclude=MedicationDispense:subject:Patient&_revinclude=MedicationRequest:patient&_id=patientId
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "subject");
- var revincludeMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", "Patient", null, false, true, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var revincludeMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Patient", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "patientId", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("subject", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneRevIncludeAndOneRevIncludeIterate_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/Practitioner?_revinclude:iterate=MedicationRequest:patient&_revinclude=Patient:general-practitioner&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var revincludeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var revincludePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Practitioner", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithOneRevIncludeAndOneIncludeIterate_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationRequest?_include:iterate=MedicationDispense:patient&_revinclude=MedicationDispense:prescription&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeIterateMedicationDispensePatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var revincludeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationDispensePatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revincludeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- // Queries with indirect dependencies
- // All possible permutations of 3 parameters: _include=MedicationDispense:prescription&_include:iterate=MedicationRequest:patient&_include:iterate=Patient:general-practitioner
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesFirstPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include=MedicationDispense:prescription&_include:iterate=MedicationRequest:patient&_include:iterate=Patient:general-practitioner&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesSecondPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include=MedicationDispense:prescription&_include:iterate=Patient:general-practitioner&_include:iterate=MedicationRequest:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesThirdPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=MedicationRequest:patient&_include=MedicationDispense:prescription&_include:iterate=Patient:general-practitioner&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesFourthPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=MedicationRequest:patient&_include:iterate=Patient:general-practitioner&_include=MedicationDispense:prescription&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesFifthPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner&_include=MedicationDispense:prescription&_include:iterate=MedicationRequest:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithThreeIncludesSixthPermutation_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner&_include:iterate=MedicationRequest:patient&_include=MedicationDispense:prescription&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- // Queries with multiple includes/revincludes
-
- [Fact]
- public void GivenASqlRootExpressionWithMultipleIncludes_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner&_include:iterate=MedicationRequest:patient&_include=MedicationDispense:prescription&_id=smart-MedicationDispense-567
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "smart-MedicationDispense-567", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithMultipleRevIncludes_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/Organization?_revinclude:iterate=MedicationDispense:prescription&_revinclude:iterate=MedicationRequest:patient&_revinclude=Patient:organization&_id=organization-id
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "organization");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Organization", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "organization-id", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("organization", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithMultipleIncludesAndRevIncludes_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/Organization?_include:iterate=MedicationDispense:prescription&_revinclude:iterate=MedicationDispense:patient&_revinclude=Patient:organization&_id=organization-id
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "organization");
- var includeMedicationDispensePrescription = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Organization", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "organization-id", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePrescription, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("organization", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("prescription", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- // Queries with search parameters unrelated to the query
- [Fact]
- public void GivenASqlRootExpressionWithParametersUnrelatedToTheQuery_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_id=12345&_include:iterate=Device:location&_include:iterate=Location:endpoint&_include=MedicationDispense:performer&_include:iterate=Patient:general-practitioner
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Device", "location");
- var includeIterateDeviceLocation = new IncludeExpression(new[] { "Device" }, refSearchParameter, "Device", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Location", "endpoint");
- var includeIterateLocationEndpoint = new IncludeExpression(new[] { "Location" }, refSearchParameter, "Location", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "performer");
- var includeMedicationDispensePerformer = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Organization", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateDeviceLocation, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateLocationEndpoint, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePerformer, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientPractitioner, SearchParamTableExpressionKind.Include),
-
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(11, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("performer", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("Device", includeExpression.ResourceTypes[0]);
- Assert.Equal("location", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Location", includeExpression.ResourceTypes[0]);
- Assert.Equal("endpoint", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[8].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[8].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[9].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[10].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithParametersUnrelatedToTheQuerySortedDiferently_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_id=12345&_include:iterate=Location:endpoint&_include=MedicationDispense:performer&_include:iterate=Patient:general-practitioner&_include:iterate=Device:location
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Device", "location");
- var includeIterateDeviceLocation = new IncludeExpression(new[] { "Device" }, refSearchParameter, "Device", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Location", "endpoint");
- var includeIterateLocationEndpoint = new IncludeExpression(new[] { "Location" }, refSearchParameter, "Location", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "performer");
- var includeMedicationDispensePerformer = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "Organization", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateLocationEndpoint, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispensePerformer, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateDeviceLocation, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(11, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("performer", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[9].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Device", includeExpression.ResourceTypes[0]);
- Assert.Equal("location", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[8].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[8].Predicate;
- Assert.Equal("Location", includeExpression.ResourceTypes[0]);
- Assert.Equal("endpoint", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[9].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[10].Kind);
- }
-
- // Wildcard Queries
-
- [Fact]
- public void GivenASqlRootExpressionWithIncludeWildcard_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationDispense?_include:iterate=Patient:general-practitioner&_include:iterate=MedicationRequest:patient&_include=MedicationDispense:*&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientGeneralPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- var referencedTypes = new List { "Location", "MedicationRequest", "Patient", "Practitioner", "Organization" }; // partial list of referenced types
- var includeMedicationDispenseWildcard = new IncludeExpression(new[] { "MedicationDispense" }, null, "MedicationDispense", null, referencedTypes, true, false, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientGeneralPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispenseWildcard, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(9, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.True(includeExpression.WildCard);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationRequest", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[6].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[6].Predicate;
- Assert.Equal("Patient", includeExpression.ResourceTypes[0]);
- Assert.Equal("general-practitioner", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[7].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[8].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithRevIncludeWildcard_WhenVisitedByIncludeRewriter_TheExpressionsShouldBeOrderedCorrectly()
- {
- // Order the following query:
- // [base]/MedicationRequest?_include:iterate=MedicationDispense:patient&_revinclude=MedicationDispense:*&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, true);
-
- var referencedTypes = new List { "Location", "MedicationRequest", "Patient", "Practitioner", "Organization" }; // partial list of referenced types
- var revIncludeMedicationDispenseWildcard = new IncludeExpression(new[] { "MedicationDispense" }, null, "MedicationDispense", null, referencedTypes, true, true, false);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationRequest", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revIncludeMedicationDispenseWildcard, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- var orderedExpressions = ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions;
-
- // Assert the number of expressions and their order is correct, including IncludeUnionAll expression, which was added in the IncludeRewriter visit.
- Assert.NotNull(orderedExpressions);
- Assert.Equal(7, orderedExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.All, orderedExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Top, orderedExpressions[1].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[2].Kind);
- var includeExpression = (IncludeExpression)orderedExpressions[2].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.True(includeExpression.WildCard);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[3].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, orderedExpressions[4].Kind);
- includeExpression = (IncludeExpression)orderedExpressions[4].Predicate;
- Assert.Equal("MedicationDispense", includeExpression.ResourceTypes[0]);
- Assert.Equal("patient", includeExpression.ReferenceSearchParameter.Code);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, orderedExpressions[5].Kind);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, orderedExpressions[6].Kind);
- }
-
- [Fact]
- public void GivenASqlRootExpressionWithCyclicIncludeIterate_WhenVisitedByIncludeRewriter_AnErrorIsExpected()
- {
- // Order the following cyclic query:
- // [base]/MedicationDispense?_include=MedicationDispense:prescription&_include:iterate=MedicationRequest:patient&_include:iterate=Patient:general-practitioner&_revinclude:iterate=DiagnosticReport:performer:Practitioner&_include:iterate=DiagnosticReport:patient&_id=12345
-
- var refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationDispense", "prescription");
- var includeMedicationDispense = new IncludeExpression(new[] { "MedicationDispense" }, refSearchParameter, "MedicationDispense", null, null, false, false, false);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("MedicationRequest", "patient");
- var includeIterateMedicationRequestPatient = new IncludeExpression(new[] { "MedicationRequest" }, refSearchParameter, "MedicationRequest", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("Patient", "general-practitioner");
- var includeIteratePatientPractitioner = new IncludeExpression(new[] { "Patient" }, refSearchParameter, "Patient", null, null, false, false, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("DiagnosticReport", "performer");
- var revIncludeIterateDiagnosticReportPerformer = new IncludeExpression(new[] { "DiagnosticReport" }, refSearchParameter, "DiagnosticReport", "Practitioner", null, false, true, true);
-
- refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter("DiagnosticReport", "patient");
- var includeIterateDiagnosticReportPatient = new IncludeExpression(new[] { "DiagnosticReport" }, refSearchParameter, "DiagnosticReport", null, null, false, false, true);
-
- Expression predicate = Expression.And(new List
- {
- new SearchParameterExpression(new SearchParameterInfo("_type", "_type"), new StringExpression(StringOperator.Equals, FieldName.String, null, "MedicationDispense", false)),
- new SearchParameterExpression(new SearchParameterInfo("_id", "_id"), new StringExpression(StringOperator.Equals, FieldName.String, null, "12345", false)),
- });
-
- var sqlExpression = new SqlRootExpression(
- new List
- {
- new SearchParamTableExpression(null, predicate, SearchParamTableExpressionKind.All),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeMedicationDispense, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateMedicationRequestPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIteratePatientPractitioner, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, revIncludeIterateDiagnosticReportPerformer, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(IncludeQueryGenerator.Instance, includeIterateDiagnosticReportPatient, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- },
- new List());
-
- Assert.Throws(() => ((SqlRootExpression)sqlExpression.AcceptVisitor(IncludeRewriter.Instance)).SearchParamTableExpressions);
- }
-
- public class IncludeRewriterFixture
- {
- private bool isInitialized = false;
-
- public IncludeRewriterFixture()
- {
- IModelInfoProvider modelInfoProvider = MockModelInfoProviderBuilder
- .Create(FhirSpecification.R4)
- .AddKnownTypes("Device", "DiagnosticReport", "MedicationRequest", "MedicationDispense", "Location", "Practitioner", "Organization", "Bundle")
- .Build();
- var mediator = Substitute.For();
- var searchService = Substitute.For();
- var searchParameterComparer = Substitute.For>();
- var statusDataStore = Substitute.For();
- var fhirDataStore = Substitute.For();
- var logger = NullLogger.Instance;
-
- SearchParameterDefinitionManager = new SearchParameterDefinitionManager(
- modelInfoProvider,
- mediator,
- searchService.CreateMockScopeProvider(),
- searchParameterComparer,
- statusDataStore.CreateMockScopeProvider(),
- fhirDataStore.CreateMockScopeProvider(),
- logger);
- }
-
- public ISearchParameterDefinitionManager SearchParameterDefinitionManager { get; }
-
- public async Task Start()
- {
- if (!isInitialized)
- {
- await ((SearchParameterDefinitionManager)SearchParameterDefinitionManager).EnsureInitializedAsync(CancellationToken.None);
- isInitialized = true;
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludesOperationRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludesOperationRewriterTests.cs
deleted file mode 100644
index 9f95ffccde..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/IncludesOperationRewriterTests.cs
+++ /dev/null
@@ -1,440 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- ///
- /// Unit tests for IncludesOperationRewriter.
- /// Tests the rewriter's ability to reorder include expressions for the $includes operation,
- /// adding IncludeUnionAll and IncludeLimit expressions at the end.
- /// Key difference from IncludeRewriter: Does NOT add IncludeLimit after each include expression.
- ///
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class IncludesOperationRewriterTests
- {
- [Fact]
- public void GivenNullExpression_WhenRewritten_ThenReturnsNull()
- {
- // Act
- var result = IncludesOperationRewriter.Instance.VisitSqlRoot(null, null);
-
- // Assert
- Assert.Null(result);
- }
-
- [Fact]
- public void GivenSingleExpression_WhenRewritten_ThenReturnsUnchanged()
- {
- // Arrange - Only one expression
- var normalExpression = Expression.Equals(FieldName.TokenCode, null, "test");
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, normalExpression, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Should return unchanged (count == 1)
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenNoIncludeExpressions_WhenRewritten_ThenReturnsUnchanged()
- {
- // Arrange - No include expressions
- var expr1 = Expression.Equals(FieldName.TokenCode, null, "code1");
- var expr2 = Expression.Equals(FieldName.String, null, "value2");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, expr1, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, expr2, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - No includes, should return unchanged
- Assert.Same(sqlRoot, result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenMixedExpressionsWithIncludes_WhenRewritten_ThenIncludesMovedToEnd()
- {
- // Arrange - Normal expression followed by include
- var normalExpr = Expression.Equals(FieldName.TokenCode, null, "test");
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, normalExpr, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Should reorder: Normal, Include, IncludeUnionAll, IncludeLimit
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[3].Kind);
- }
-
- [Fact]
- public void GivenIncludesOperationRewriter_WhenProcessingIncludes_ThenNoLimitAfterEachInclude()
- {
- // Arrange - Multiple includes (key difference from base IncludeRewriter)
- var include1 = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var include2 = CreateIncludeExpression("Patient", "Practitioner", "general-practitioner", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, include1, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, include2, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - IncludesOperationRewriter should NOT add IncludeLimit after each include
- // Expected: Include1, Include2, IncludeUnionAll, IncludeLimit (4 total, not 6)
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[3].Kind);
- }
-
- [Fact]
- public void GivenIncludeIterateExpressions_WhenRewritten_ThenSortedCorrectly()
- {
- // Arrange - Include iterate should come after regular include
- var include = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var includeIterate = CreateIncludeExpression("Observation", "Practitioner", "performer", reversed: false, iterate: true, sourceTypeOverrideForIterate: "Observation");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeIterate, SearchParamTableExpressionKind.Include), // Out of order
- new SearchParamTableExpression(null, include, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Include iterate should be sorted after regular include
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- // Regular include should come first
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[0].Kind);
- var firstInclude = (IncludeExpression)result.SearchParamTableExpressions[0].Predicate;
- Assert.False(firstInclude.Iterate);
-
- // Include iterate should come second
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- var secondInclude = (IncludeExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.True(secondInclude.Iterate);
-
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[3].Kind);
- }
-
- [Fact]
- public void GivenNormalAndIncludeExpressions_WhenRewritten_ThenNormalComesFirst()
- {
- // Arrange - Include before normal (should be reordered)
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var normalExpr = Expression.Equals(FieldName.TokenCode, null, "test");
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, normalExpr, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Normal should be reordered to come first
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
- Assert.Same(normalExpr, result.SearchParamTableExpressions[0].Predicate);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Same(includeExpr, result.SearchParamTableExpressions[1].Predicate);
- }
-
- [Fact]
- public void GivenMultipleNormalAndIncludeExpressions_WhenRewritten_ThenCorrectOrder()
- {
- // Arrange - Mix of normal and include expressions
- var normal1 = Expression.Equals(FieldName.TokenCode, null, "code1");
- var include1 = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var normal2 = Expression.Equals(FieldName.String, null, "value2");
- var include2 = CreateIncludeExpression("Patient", "Practitioner", "general-practitioner", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, include1, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, normal1, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, include2, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, normal2, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - All normals first, then all includes, then union/limit
- Assert.Equal(6, result.SearchParamTableExpressions.Count);
-
- // Normals
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
- Assert.Same(normal1, result.SearchParamTableExpressions[0].Predicate);
-
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[1].Kind);
- Assert.Same(normal2, result.SearchParamTableExpressions[1].Predicate);
-
- // Includes
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[2].Kind);
- Assert.Same(include1, result.SearchParamTableExpressions[2].Predicate);
-
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[3].Kind);
- Assert.Same(include2, result.SearchParamTableExpressions[3].Predicate);
-
- // Union and Limit
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[4].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[5].Kind);
- }
-
- [Fact]
- public void GivenChainExpression_WhenRewritten_ThenChainPreservedBeforeIncludes()
- {
- // Arrange - Chain and include expressions
- var chainExpr = Expression.Equals(FieldName.String, null, "chain");
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, chainExpr, SearchParamTableExpressionKind.Chain),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Chain should come before include
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Chain, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[3].Kind);
- }
-
- [Fact]
- public void GivenTopExpression_WhenRewritten_ThenTopPreservedBeforeIncludes()
- {
- // Arrange
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Top),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Top should come before include
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Top, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenSortExpression_WhenRewritten_ThenSortPreservedBeforeIncludes()
- {
- // Arrange
- var sortExpr = Expression.Equals(FieldName.DateTimeStart, null, System.DateTimeOffset.UtcNow);
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, sortExpr, SearchParamTableExpressionKind.Sort),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Sort should come before include
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Sort, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Include, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenResourceTableExpressions_WhenRewritten_ThenResourceExpressionsPreserved()
- {
- // Arrange
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- };
-
- var resourceExpr = Expression.SearchParameter(
- new SearchParameterInfo("_type", "_type"),
- Expression.Equals(FieldName.TokenCode, null, "Patient"));
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List { resourceExpr });
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Resource table expressions should be preserved
- Assert.Single(result.ResourceTableExpressions);
- Assert.Same(resourceExpr, result.ResourceTableExpressions[0]);
- }
-
- [Fact]
- public void GivenComplexIncludeIterateDependencies_WhenRewritten_ThenSortedByDependency()
- {
- // Arrange - Create includes with dependencies: Observation -> Device -> Location
- var include1 = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
- var includeIterate1 = CreateIncludeExpression("Observation", "Device", "device", reversed: false, iterate: true, sourceTypeOverrideForIterate: "Observation");
- var includeIterate2 = CreateIncludeExpression("Device", "Location", "location", reversed: false, iterate: true, sourceTypeOverrideForIterate: "Device");
-
- var tableExpressions = new List
- {
- // Add in wrong order
- new SearchParamTableExpression(null, includeIterate2, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, include1, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, includeIterate1, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert - Should be sorted by dependency chain
- Assert.Equal(5, result.SearchParamTableExpressions.Count);
-
- // Regular include first
- var expr0 = (IncludeExpression)result.SearchParamTableExpressions[0].Predicate;
- Assert.False(expr0.Iterate);
- Assert.Equal("Observation", expr0.TargetResourceType);
-
- // Then iterate to Device (depends on Observation)
- var expr1 = (IncludeExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.True(expr1.Iterate);
- Assert.Equal("Device", expr1.TargetResourceType);
-
- // Then iterate to Location (depends on Device)
- var expr2 = (IncludeExpression)result.SearchParamTableExpressions[2].Predicate;
- Assert.True(expr2.Iterate);
- Assert.Equal("Location", expr2.TargetResourceType);
- }
-
- [Fact]
- public void GivenRewriterInstance_WhenAccessed_ThenNotNull()
- {
- // Assert - Verify singleton instance exists
- Assert.NotNull(IncludesOperationRewriter.Instance);
- }
-
- [Fact]
- public void GivenOnlyIncludeExpression_WhenRewritten_ThenAddsUnionAndLimit()
- {
- // Arrange - Only include, no other expressions
- var includeExpr = CreateIncludeExpression("Patient", "Observation", "subject", reversed: false, iterate: false);
-
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, includeExpr, SearchParamTableExpressionKind.Include),
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal), // Need at least 2 for rewrite
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(IncludesOperationRewriter.Instance, null);
-
- // Assert
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
- Assert.Equal(SearchParamTableExpressionKind.IncludeUnionAll, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.IncludeLimit, result.SearchParamTableExpressions[3].Kind);
- }
-
- private static IncludeExpression CreateIncludeExpression(
- string sourceType,
- string targetType,
- string searchParameter,
- bool reversed,
- bool iterate,
- string sourceTypeOverrideForIterate = null)
- {
- var referenceSearchParam = new SearchParameterInfo(searchParameter, searchParameter)
- {
- Type = ValueSets.SearchParamType.Reference,
- };
-
- // For iterate expressions, use the source type override if provided
- string actualSourceType = iterate && sourceTypeOverrideForIterate != null ? sourceTypeOverrideForIterate : sourceType;
-
- return new IncludeExpression(
- new[] { actualSourceType },
- referenceSearchParam,
- actualSourceType,
- targetType,
- new[] { targetType },
- wildCard: false,
- iterate: iterate,
- reversed: reversed,
- allowedResourceTypesByScope: null);
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs
deleted file mode 100644
index 9a7f8e40bd..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/LastUpdatedToResourceSurrogateIdRewriterTests.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.SqlServer.Features.Storage;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class LastUpdatedToResourceSurrogateIdRewriterTests
- {
- [InlineData(BinaryOperator.GreaterThan, "2020-09-24T12:00:00.500Z", BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.501Z")]
- [InlineData(BinaryOperator.GreaterThan, "2020-09-24T12:00:00.5001Z", BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.501Z")]
- [InlineData(BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.500Z", BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.500Z")]
- [InlineData(BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.5001Z", BinaryOperator.GreaterThanOrEqual, "2020-09-24T12:00:00.501Z")]
- [InlineData(BinaryOperator.LessThan, "2020-09-24T12:00:00.500Z", BinaryOperator.LessThan, "2020-09-24T12:00:00.500Z")]
- [InlineData(BinaryOperator.LessThan, "2020-09-24T12:00:00.5001Z", BinaryOperator.LessThan, "2020-09-24T12:00:00.501Z")] // will yield 500, 499
- [InlineData(BinaryOperator.LessThanOrEqual, "2020-09-24T12:00:00.500Z", BinaryOperator.LessThan, "2020-09-24T12:00:00.501Z")]
- [InlineData(BinaryOperator.LessThanOrEqual, "2020-09-24T12:00:00.5001Z", BinaryOperator.LessThan, "2020-09-24T12:00:00.501Z")] // will yield 500, 499
- [Theory]
- public void GivenAnExpressionOverLastUpdated_WhenTranslatedToResourceSurrogateId_HasCorrectRanges(BinaryOperator inputOperator, string inputDateTimeOffset, BinaryOperator expectedOperator, string expectedDateTimeOffset)
- {
- var input = new BinaryExpression(inputOperator, FieldName.DateTimeStart, null, DateTimeOffset.Parse(inputDateTimeOffset));
-
- var output = input.AcceptVisitor(LastUpdatedToResourceSurrogateIdRewriter.Instance, null);
-
- BinaryExpression binaryOutput = Assert.IsType(output);
- Assert.Equal(SqlFieldName.ResourceSurrogateId, binaryOutput.FieldName);
- Assert.Equal(expectedOperator, binaryOutput.BinaryOperator);
- Assert.Equal(DateTimeOffset.Parse(expectedDateTimeOffset), ((long)binaryOutput.Value).ToLastUpdated());
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/MissingSearchParamVisitorTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/MissingSearchParamVisitorTests.cs
deleted file mode 100644
index 3ba7cebb56..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/MissingSearchParamVisitorTests.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class MissingSearchParamVisitorTests
- {
- [Fact]
- public void GivenExpressionWithMissingParameterExpression_WhenVisited_AllExpressionPrependedToExpressionList()
- {
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, new MissingSearchParameterExpression(new SearchParameterInfo("TestParam", "TestParam"), true), SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(tableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(MissingSearchParamVisitor.Instance);
- Assert.Collection(
- visitedExpression.SearchParamTableExpressions,
- e => { Assert.Equal(SearchParamTableExpressionKind.All, e.Kind); },
- e => { Assert.NotNull(e.Predicate as MissingSearchParameterExpression); });
- Assert.Equal(tableExpressions.Count + 1, visitedExpression.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenExpressionWithNoMissingParameterExpression_WhenVisited_OriginalExpressionReturned()
- {
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(tableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(MissingSearchParamVisitor.Instance);
- Assert.Equal(inputExpression, visitedExpression);
- }
-
- [Fact]
- public void GivenExpressionWithMissingParameterExpressionFalseLast_WhenVisited_OriginalExpressionReturned()
- {
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, new MissingSearchParameterExpression(new SearchParameterInfo("TestParam", "TestParam"), false), SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(tableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(MissingSearchParamVisitor.Instance);
- Assert.Equal(inputExpression, visitedExpression);
- }
-
- [Fact]
- public void GivenExpressionWithMissingParameterExpressionLast_WhenVisited_MissingParameterExpressionNegated()
- {
- var tableExpressions = new List
- {
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, new MissingSearchParameterExpression(new SearchParameterInfo("TestParam", "TestParam"), true), SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(tableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(MissingSearchParamVisitor.Instance);
- Assert.Collection(
- visitedExpression.SearchParamTableExpressions,
- e => { Assert.Equal(tableExpressions[0], e); },
- e => { Assert.Equal(SearchParamTableExpressionKind.NotExists, e.Kind); });
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotExpressionRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotExpressionRewriterTests.cs
deleted file mode 100644
index 6d67367e80..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotExpressionRewriterTests.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Collections.Generic;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class NotExpressionRewriterTests
- {
- [Fact]
- public void GivenExpressionWithNotExpression_WhenVisited_AllExpressionPrependedToExpressionList()
- {
- var subExpression = Expression.StringEquals(FieldName.TokenCode, 0, "TestValue123", false);
- var searchParamTableExpressions = new List
- {
- new SearchParamTableExpression(null, new SearchParameterExpression(new SearchParameterInfo("TestParam", "TestParam"), Expression.Not(subExpression)), SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(searchParamTableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(NotExpressionRewriter.Instance);
- Assert.Collection(
- visitedExpression.SearchParamTableExpressions,
- e => { Assert.Equal(SearchParamTableExpressionKind.Normal, e.Kind); },
- e => { ValidateNotExpression(subExpression, e); });
- Assert.Equal(searchParamTableExpressions.Count + 1, visitedExpression.SearchParamTableExpressions.Count);
- }
-
- [Fact]
- public void GivenExpressionWithNoNotExpression_WhenVisited_OriginalExpressionReturned()
- {
- var searchParamTableExpressions = new List
- {
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(searchParamTableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(NotExpressionRewriter.Instance);
- Assert.Equal(inputExpression, visitedExpression);
- }
-
- [Fact]
- public void GivenExpressionWithNotExpressionLast_WhenVisited_NotExpressionUnwrapped()
- {
- var subExpression = Expression.StringEquals(FieldName.TokenCode, 0, "TestValue123", false);
- var searchParamTableExpressions = new List
- {
- new SearchParamTableExpression(null, null, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, new SearchParameterExpression(new SearchParameterInfo("TestParam", "TestParam"), Expression.Not(subExpression)), SearchParamTableExpressionKind.Normal),
- };
-
- var inputExpression = SqlRootExpression.WithSearchParamTableExpressions(searchParamTableExpressions);
- var visitedExpression = (SqlRootExpression)inputExpression.AcceptVisitor(NotExpressionRewriter.Instance);
- Assert.Collection(
- visitedExpression.SearchParamTableExpressions,
- e => { Assert.Equal(searchParamTableExpressions[0], e); },
- e => { ValidateNotExpression(subExpression, e); });
- }
-
- private static void ValidateNotExpression(Expression subExpression, SearchParamTableExpression expressionToValidate)
- {
- Assert.Equal(SearchParamTableExpressionKind.NotExists, expressionToValidate.Kind);
-
- var spExpression = Assert.IsType(expressionToValidate.Predicate);
- Assert.Equal(subExpression, spExpression.Expression);
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotReferencingSqlGenerationTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotReferencingSqlGenerationTests.cs
deleted file mode 100644
index 2389e61d42..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NotReferencingSqlGenerationTests.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using System.Text;
-using Microsoft.Data.SqlClient;
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.Core.Models;
-using Microsoft.Health.Fhir.SqlServer.Features.Schema;
-using Microsoft.Health.Fhir.SqlServer.Features.Search;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors.QueryGenerators;
-using Microsoft.Health.Fhir.SqlServer.Features.Storage;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.SqlServer;
-using Microsoft.Health.SqlServer.Features.Schema;
-using Microsoft.Health.SqlServer.Features.Storage;
-using Microsoft.Health.Test.Utilities;
-using NSubstitute;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class NotReferencingSqlGenerationTests
- {
- [Fact]
- public void GivenNotReferencingExpression_WhenVisited_ThenNotExistsSqlIsGenerated()
- {
- var model = Substitute.For();
- model.GetResourceTypeId("Device").Returns((short)99);
- var patientParam = new SearchParameterInfo(
- "patient",
- "patient",
- ValueSets.SearchParamType.Reference,
- new System.Uri("http://hl7.org/fhir/SearchParameter/Device-patient"));
- model.GetSearchParamId(patientParam.Url).Returns((short)123);
-
- var stringBuilder = new IndentedStringBuilder(new StringBuilder());
- using var sqlCommand = new SqlCommand();
- var parameters = new HashingSqlQueryParameterManager(new SqlQueryParameterManager(sqlCommand.Parameters));
- var schemaInformation = new SchemaInformation(SchemaVersionConstants.Min, SchemaVersionConstants.Max) { Current = SchemaVersionConstants.Max };
- var context = new SearchParameterQueryGeneratorContext(stringBuilder, parameters, model, schemaInformation, isAsyncOperation: false, tableAlias: null);
-
- var expression = new NotReferencingExpression("Device", patientParam);
- expression.AcceptVisitor(NotReferencedQueryGenerator.Instance, context);
-
- var sql = stringBuilder.ToString();
- Assert.Contains("= 99", sql); // ResourceTypeId filter
- Assert.Contains("NOT EXISTS", sql); // anti-join
- Assert.Contains("SearchParamId = 123", sql);
- Assert.Contains("RefResourceSurrogateId = ResourceSurrogateId", sql);
- }
- }
-}
diff --git a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NumericRangeRewriterTests.cs b/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NumericRangeRewriterTests.cs
deleted file mode 100644
index 5509bb629e..0000000000
--- a/src/Microsoft.Health.Fhir.SqlServer.UnitTests/Features/Search/Expressions/NumericRangeRewriterTests.cs
+++ /dev/null
@@ -1,411 +0,0 @@
-// -------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
-// -------------------------------------------------------------------------------------------------
-
-using Microsoft.Health.Fhir.Core.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions;
-using Microsoft.Health.Fhir.SqlServer.Features.Search.Expressions.Visitors;
-using Microsoft.Health.Fhir.Tests.Common;
-using Microsoft.Health.Test.Utilities;
-using Xunit;
-
-namespace Microsoft.Health.Fhir.SqlServer.UnitTests.Features.Search.Expressions
-{
- ///
- /// Unit tests for NumericRangeRewriter.
- /// Tests the rewriter's ability to transform numeric and quantity expressions
- /// to account for range values (low/high fields).
- ///
- [Trait(Traits.OwningTeam, OwningTeam.Fhir)]
- [Trait(Traits.Category, Categories.Search)]
- public class NumericRangeRewriterTests
- {
- [Fact]
- public void GivenQuantityGreaterThanExpression_WhenRewritten_ThenUsesQuantityHighField()
- {
- // Arrange
- var expression = Expression.GreaterThan(FieldName.Quantity, null, 5.0m);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- // First expression is the original
- var originalExpr = (BinaryExpression)result.SearchParamTableExpressions[0].Predicate;
- Assert.Equal(FieldName.Quantity, originalExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThan, originalExpr.BinaryOperator);
-
- // Second expression is the concatenation using QuantityHigh
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.QuantityHigh, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThan, concatenationExpr.BinaryOperator);
- Assert.Equal(5.0m, concatenationExpr.Value);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
- }
-
- [Fact]
- public void GivenQuantityGreaterThanOrEqualExpression_WhenRewritten_ThenUsesQuantityHighField()
- {
- // Arrange
- var expression = Expression.GreaterThanOrEqual(FieldName.Quantity, null, 10.5m);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.QuantityHigh, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThanOrEqual, concatenationExpr.BinaryOperator);
- Assert.Equal(10.5m, concatenationExpr.Value);
- }
-
- [Fact]
- public void GivenQuantityLessThanExpression_WhenRewritten_ThenUsesQuantityLowField()
- {
- // Arrange
- var expression = Expression.LessThan(FieldName.Quantity, null, 100.0m);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.QuantityLow, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.LessThan, concatenationExpr.BinaryOperator);
- Assert.Equal(100.0m, concatenationExpr.Value);
- }
-
- [Fact]
- public void GivenQuantityLessThanOrEqualExpression_WhenRewritten_ThenUsesQuantityLowField()
- {
- // Arrange
- var expression = Expression.LessThanOrEqual(FieldName.Quantity, null, 50.25m);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.QuantityLow, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.LessThanOrEqual, concatenationExpr.BinaryOperator);
- Assert.Equal(50.25m, concatenationExpr.Value);
- }
-
- [Fact]
- public void GivenNumberGreaterThanExpression_WhenRewritten_ThenUsesNumberHighField()
- {
- // Arrange
- var expression = Expression.GreaterThan(FieldName.Number, null, 42);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.NumberHigh, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThan, concatenationExpr.BinaryOperator);
- Assert.Equal(42, concatenationExpr.Value);
- }
-
- [Fact]
- public void GivenNumberLessThanExpression_WhenRewritten_ThenUsesNumberLowField()
- {
- // Arrange
- var expression = Expression.LessThan(FieldName.Number, null, 99);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.NumberLow, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.LessThan, concatenationExpr.BinaryOperator);
- }
-
- [Fact]
- public void GivenNumberGreaterThanOrEqualExpression_WhenRewritten_ThenUsesNumberHighField()
- {
- // Arrange
- var expression = Expression.GreaterThanOrEqual(FieldName.Number, null, 0);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.NumberHigh, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.GreaterThanOrEqual, concatenationExpr.BinaryOperator);
- }
-
- [Fact]
- public void GivenNumberLessThanOrEqualExpression_WhenRewritten_ThenUsesNumberLowField()
- {
- // Arrange
- var expression = Expression.LessThanOrEqual(FieldName.Number, null, 1000);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.NumberLow, concatenationExpr.FieldName);
- Assert.Equal(BinaryOperator.LessThanOrEqual, concatenationExpr.BinaryOperator);
- }
-
- [Fact]
- public void GivenNonNumericExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange - String expression should not be rewritten
- var expression = Expression.Equals(FieldName.String, null, "test");
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert - Should return same expression
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenTokenCodeExpression_WhenRewritten_ThenNoRewriteOccurs()
- {
- // Arrange
- var expression = Expression.Equals(FieldName.TokenCode, null, "code");
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenQuantityWithComponentIndex_WhenRewritten_ThenComponentIndexPreserved()
- {
- // Arrange
- var componentIndex = 1;
- var expression = Expression.GreaterThan(FieldName.Quantity, componentIndex, 5.0m);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.QuantityHigh, concatenationExpr.FieldName);
- Assert.Equal(componentIndex, concatenationExpr.ComponentIndex);
- }
-
- [Fact]
- public void GivenNumberWithComponentIndex_WhenRewritten_ThenComponentIndexPreserved()
- {
- // Arrange
- var componentIndex = 2;
- var expression = Expression.LessThan(FieldName.Number, componentIndex, 100);
- var sqlRoot = CreateSqlRootWithExpression(expression);
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Equal(2, result.SearchParamTableExpressions.Count);
-
- var concatenationExpr = (BinaryExpression)result.SearchParamTableExpressions[1].Predicate;
- Assert.Equal(SqlFieldName.NumberLow, concatenationExpr.FieldName);
- Assert.Equal(componentIndex, concatenationExpr.ComponentIndex);
- }
-
- [Fact]
- public void GivenMultipleNumericExpressions_WhenRewritten_ThenAllAreRewritten()
- {
- // Arrange
- var expr1 = Expression.GreaterThan(FieldName.Number, null, 10);
- var expr2 = Expression.LessThan(FieldName.Quantity, null, 50.0m);
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, expr1, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, expr2, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert - Each expression should be doubled (original + concatenation)
- Assert.Equal(4, result.SearchParamTableExpressions.Count);
-
- // First original + concatenation
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
-
- // Second original + concatenation
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[2].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[3].Kind);
- }
-
- [Fact]
- public void GivenMixedNumericAndNonNumericExpressions_WhenRewritten_ThenOnlyNumericRewritten()
- {
- // Arrange
- var numericExpr = Expression.GreaterThan(FieldName.Number, null, 5);
- var stringExpr = Expression.Equals(FieldName.String, null, "test");
-
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, numericExpr, SearchParamTableExpressionKind.Normal),
- new SearchParamTableExpression(null, stringExpr, SearchParamTableExpressionKind.Normal),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert - Numeric gets concatenation, string does not
- Assert.Equal(3, result.SearchParamTableExpressions.Count);
-
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[0].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Concatenation, result.SearchParamTableExpressions[1].Kind);
- Assert.Equal(SearchParamTableExpressionKind.Normal, result.SearchParamTableExpressions[2].Kind);
- Assert.Same(stringExpr, result.SearchParamTableExpressions[2].Predicate);
- }
-
- [Fact]
- public void GivenEmptySqlRoot_WhenRewritten_ThenReturnsUnchanged()
- {
- // Arrange
- var sqlRoot = new SqlRootExpression(
- new System.Collections.Generic.List(),
- new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- }
-
- [Fact]
- public void GivenChainExpression_WhenRewritten_ThenSkipsChainExpressions()
- {
- // Arrange - Chain expressions should be skipped by ConcatenationRewriter
- var expression = Expression.GreaterThan(FieldName.Number, null, 10);
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, expression, SearchParamTableExpressionKind.Chain),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert - Chain expression should not be rewritten
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenIncludeExpression_WhenRewritten_ThenSkipsIncludeExpressions()
- {
- // Arrange
- var expression = Expression.GreaterThan(FieldName.Number, null, 10);
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, expression, SearchParamTableExpressionKind.Include),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List());
-
- // Act
- var result = (SqlRootExpression)sqlRoot.AcceptVisitor(NumericRangeRewriter.Instance, null);
-
- // Assert
- Assert.Same(sqlRoot, result);
- Assert.Single(result.SearchParamTableExpressions);
- }
-
- [Fact]
- public void GivenSortExpression_WhenRewritten_ThenSkipsSortExpressions()
- {
- // Arrange
- var expression = Expression.GreaterThan(FieldName.Number, null, 10);
- var tableExpressions = new System.Collections.Generic.List
- {
- new SearchParamTableExpression(null, expression, SearchParamTableExpressionKind.Sort),
- };
-
- var sqlRoot = new SqlRootExpression(tableExpressions, new System.Collections.Generic.List