Skip to content

Initial new parser implementation - #5751

Open
Robert Johnson (LTA-Thinking) wants to merge 60 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 60 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 23 commits July 21, 2026 14:54
Web-based debugging tool that visualizes FHIR URL to SQL query
transformations using the new SearchParameterSqlParser. Runs at
http://localhost:5200 with a GUI for entering FHIR URLs and viewing
the generated SQL with syntax highlighting.

- Mocks ISqlServerFhirModel with auto-assigned IDs
- Loads R4 search parameters from embedded resources
- Supports all query types: search, sort, chain, include, iterate,
  _not-referenced, and continuation tokens
- No SQL database connection required

Usage: dotnet run --project tools/SqlSearchDebugger

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Move FakeSqlServerFhirModel to Mocks/FakeSqlServerFhirModel.cs
- Move FakeMediator to Mocks/FakeMediator.cs
- Move FakeScopeProvider, FakeScoped, FakeSearchParameterComparer to Mocks/FakeServiceProviders.cs
- Extract ParseFhirUrl and helpers to ParserHelpers.cs
- Split web page into wwwroot/index.html, style.css, app.js
- Slim down Program.cs to app setup and route registration

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When sort has two phases (ascending/descending with missing values) and
phase 1 exhausts the include budget (IncludeCount = 0), phase 2 was not
generating an IncludesContinuationToken because the condition checked
includedResources.Count > IncludeCount (0 > 0 = false).

Add IncludeContinuationTokenSearch to the trigger condition so phase 2
still calls SearchIncludeImpl to produce a continuation token, even
though it returns 0 inline included resources. The continuation token
is then merged with phase 1's token and the \ operation pages
through both phases' includes correctly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When sort has two phases (ascending/descending with missing values) and
phase 1 exhausts the include budget (IncludeCount = 0), phase 2 was not
generating an IncludesContinuationToken. The IncludeContinuationTokenSearch
flag is not propagated through the SearchOptions copy constructor, so the
condition inside SearchImpl never triggers for phase 2.

Fix by creating the continuation token directly at the sort two-phase
level: after phase 2 returns, if IncludeContinuationTokenSearch is true
and no continuation token was produced, derive one from phase 2's matched
resource surrogate ID range. This token is then merged with phase 1's
token, allowing the \ operation to page through both phases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rewrite CompartmentSqlParser to use ReferenceSearchParam table instead of
the unused CompartmentAssignment table. The parser now queries the
ICompartmentDefinitionManager for applicable resource types and search
parameters, then generates SQL that joins on ReferenceSearchParam
filtering by compartment type and ID through the relevant reference
search parameters.

Add ICompartmentDefinitionManager as a dependency to SearchParameterSqlParser.
Add FakeCompartmentDefinitionManager mock for the debugger tool.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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
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.

2 participants