Skip to content

Initial new parser implementation - #5751

Open
Robert Johnson (LTA-Thinking) wants to merge 66 commits into
feature/new-sql-parserfrom
rojo-microsoft-fix-sql-search-parser
Open

Initial new parser implementation #5751
Robert Johnson (LTA-Thinking) wants to merge 66 commits into
feature/new-sql-parserfrom
rojo-microsoft-fix-sql-search-parser

Conversation

@LTA-Thinking

@LTA-Thinking Robert Johnson (LTA-Thinking) commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Removes the Expression based SQL parser and replaces it with one that goes directly from query parameters to SQL queries.

Related issues

Addresses User Story 199220

Testing

Existing E2E tests are utilized heavily to ensure feature parity.

FHIR Team Checklist

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

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

RB Johnson (He/Him) and others added 30 commits June 12, 2026 13:59
…terate

- BaseSqlParser: Fix column selection in chain mode to select target
  resource IDs (RefResourceTypeId/RefResourceSurrogateId) instead of
  source IDs when ChainLevel > 0
- ChainedSqlParser: Fix source join for multi-level chains, rewrite
  final CTE assembly to properly join back through ref CTE
- ReversedChainSqlParser: Complete rewrite using builder pattern,
  fix column naming (flip Ref columns for reverse direction), remove
  IsHistory/IsDeleted check on ReferenceSearchParam table
- IncludeSqlParser: Fix JoinMultiLine -> InnerJoin, support iterate
  includes without Row column dependency
- RevIncludeSqlParser: Simplify to match IncludeSqlParser structure,
  fix IsHistory/IsDeleted check on correct table alias
- ParserUtil: Add Row=0 to UNION ALL includes, add includeRow param
  for iterate dependency unions
- ParserOptions: Add IsIterateInclude flag
- SqlServerSearchService: Fix async method without await warning

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Fix SortSqlParser.CreateSortCte: remove references to IsMatch/IsPartial/Row
  columns that don't exist in the source CTE at that point
- Fix ORDER BY ORDER BY duplication: CreateOrderByClause no longer includes
  the 'ORDER BY' prefix since .OrderBy() adds it
- Fix Count CTE ordering: use SortValue for ROW_NUMBER() and ORDER BY when
  a sort CTE exists, instead of always using ResourceTypeId/ResourceSurrogateId
- Fix sort pagination: preserve ResourceSurrogateId from continuation token
  as tiebreaker in sort CTE to prevent duplicate results across pages
- Fix special sort (_lastUpdated) pagination: don't clear ContinuationToken
  when sort is a special parameter (it's needed by base CTE for filtering)
- Fix _include/_revinclude without other search params: generate base system
  CTE when only include parameters are present (prevents null return)
- Use hasSortCte flag for reliable SortValue column detection

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Fix ascending sort pagination: set SortQuerySecondPhase=true when
  continuation token has a SortValue for ascending non-_lastUpdated sorts,
  preventing phase 1 (missing=true) from re-running on subsequent pages.
- Skip two-phase sort logic when a continuation token is already present
  (not the first page).
- Fix UNION ALL column order mismatch when sort + include are combined:
  use explicit column list to ensure count CTE and include CTE columns
  align positionally.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Implement NotReferencedSqlParser that generates NOT EXISTS subqueries
against the ReferenceSearchParam table to find resources that are not
referenced by other resources. Supports:
- *:* (wildcard all - no references from any resource/param)
- <ResourceType>:* (wildcard param - no refs from specific type)
- <ResourceType>:<param> (specific resource type and search param)

Invalid formats (no colon separator) are gracefully skipped, letting
the SearchOptionsFactory handle the warning.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add ResultCteName property to ParserOptions so chain/reverse-chain
parsers communicate the actual CTE name they produce. Previously,
outer parsers assumed a fixed CTE naming pattern (cte{N}chain{level})
that broke when the inner parser was itself a chain or reverse chain,
which produces cte{N}chain{parent}_search instead.

Updated ChainedSqlParser, ReversedChainSqlParser, and BaseSqlParser
to set/read ResultCteName, fixing:
- Nested reverse chain (_has:...:_has:...)
- Reverse chain with chained search param (_has:...:device.manufacturer)
- Forward chain into reverse chain (link:Patient._has:...)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
RB Johnson (He/Him) and others added 16 commits July 23, 2026 07:33
The CompartmentSqlParser was not applying the continuation token filter,
causing paginated compartment searches to always return the same results
and never reach the end. Add ResourceSurrogateId and ResourceTypeId
filters when a continuation token is present.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace Expression tree construction with QueryParams-based approach.
Patient search parameters are added directly as query params, and
Coverage parameters are converted to _has:Coverage:beneficiary:<param>
reverse chain format. This allows the new SQL parser to handle
-match without any parser-side changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When multiple chain or reverse chain searches use the same reference
parameter (e.g., _has:Coverage:beneficiary:identifier=xxx and
_has:Coverage:beneficiary:type=yyy), the expensive ReferenceSearchParam
JOIN Resource CTE is now generated once and reused by all leaf searches
in the group.

Add ChainSearchGroup class that groups chain parameters by their
first-level reference parameter key. Update ChainedSqlParser and
ReversedChainSqlParser to accept a sharedRefCteName parameter.
Update ParseMultiple to group parameters before generating CTEs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When multiple chains share a ref CTE, each produces its own walk-back
CTE independently. Previously only the last walk-back fed into the
final query, silently discarding earlier results.

Now an intersection CTE is generated after all entries in a shared
group, INNER JOINing all walk-back CTEs to enforce AND semantics.
Single-chain groups are unaffected (no intersection CTE added).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Create SmartCompartmentSqlParser that generates UNION-based CTEs for:
  1. Resources in user's compartment (via ReferenceSearchParam)
  2. User's own resource
  3. Universal resources (Location, Organization, Practitioner, etc.)
- Add _smartCompartmentType/_smartCompartmentId to QueryParams in
  SearchOptionsFactory when AccessControlContext has a SMART compartment
- Add _fhirScopeAllowedTypes to QueryParams for fine-grained access
  control resource type restrictions
- SearchParameterSqlParser handles new params: generates SMART
  compartment CTE as base filter, applies allowed type restrictions

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Use Microsoft NuGet proxy for restore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Keep NuGet feeds in local config

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Support environment-specific NuGet sources

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@LTA-Thinking
Robert Johnson (LTA-Thinking) requested a review from a team as a code owner August 21, 2026 20:48
@LTA-Thinking Robert Johnson (LTA-Thinking) added Enhancement Enhancement on existing functionality. Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs No-PaaS-breaking-change ADR-Included ADR Included in the PR labels Aug 21, 2026
RB Johnson (He/Him) and others added 7 commits August 27, 2026 09:48
Standardize all search parsers to output ResourceTypeId before
ResourceSurrogateId, matching the order used by IncludeSqlParser,
RevIncludeSqlParser, and ParserUtil.AddUnionCte. The positional
mismatch caused UNION ALL to swap columns, breaking all include
results.

Also remove dead expression-tree code that prevented compilation of
SearchIncludeImpl.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ReferenceSqlParser now applies the type modifier (e.g., :Practitioner)
when filtering by reference target type.

SearchParameterSqlParser now validates total parameter count and
throws RequestNotValidException when exceeding 2048 values.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When multiple _has: parameters share the same resource type and reference
parameter (e.g., MemberMatch coverage conditions), combine them into a
single CTE with multiple JOINs instead of N sequential CTEs. This
matches the old stored procedure behavior where all conditions were
ANDed in one query, allowing SQL Server to use index intersection.

Falls back to sequential chaining for entries with :missing/:not
modifiers or non-standard parsers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds 151 unit tests across 17 test files covering:
- Infrastructure: SqlQueryBuilder, QueryStringParser, ChainSearchGroup, ParserUtil
- Base parsers: Token, String, DateTime, Number, Quantity, Reference, Uri
- Composite parsers: TokenString, TokenToken, TokenDateTime, TokenQuantity,
  TokenNumberNumber, ReferenceToken
- Special parsers: Id, LastUpdated, System, Sort

Tests verify SQL generation, WHERE clause building, parameter parsing,
CTE construction, and edge cases (escaping, modifiers, empty values).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment on lines +444 to +452
if (allowedTypes.Any())
{
searchOptions.QueryParams["_fhirScopeAllowedTypes"] = allowedTypes;
}
else
{
// No resource types allowed — block all queries
searchOptions.QueryParams["_fhirScopeAllowedTypes"] = new List<string> { "none" };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ADR-Included ADR Included in the PR Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Enhancement Enhancement on existing functionality. No-PaaS-breaking-change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants