Skip to content

[DO NOT MERGE] Fix string literal aliases in aggregate queries returning null on Linux + add cross-platform CI#68

Draft
McNultyyy wants to merge 5 commits into
mainfrom
mcnultyyy/fix-string-literal-aliases-linux
Draft

[DO NOT MERGE] Fix string literal aliases in aggregate queries returning null on Linux + add cross-platform CI#68
McNultyyy wants to merge 5 commits into
mainfrom
mcnultyyy/fix-string-literal-aliases-linux

Conversation

@McNultyyy
Copy link
Copy Markdown
Collaborator

@McNultyyy McNultyyy commented May 19, 2026

Summary

Fixes #67 — String literal aliases in aggregate queries (e.g. SELECT 'Settlement' AS Label, COUNT(1) AS ItemCount FROM c) returned null on Linux where ServiceInterop.dll is unavailable.

Root Cause

Two-part issue:

  1. ProjectAggregateFields (InMemoryContainer.cs): Non-aggregate SELECT fields were unconditionally treated as property paths. For 'Settlement' AS Label, this called jObj.SelectToken("'Settlement'") which returns null. Fixed by evaluating non-identifier SqlExpr nodes (literals, function calls) via EvaluateSqlExpression.

  2. DefaultQueryPlanStrategy: The multi-aggregate bypass only triggered when there were 2+ aggregates. For SELECT 42 AS X, COUNT(1) AS N (1 aggregate + 1 literal), the SDK's aggregate pipeline wasn't bypassed and crashed with "Underlying object does not have an 'payload' field". Fixed by adding a isLiteralWithAggregateBypass condition.

Why Windows vs Linux

  • Windows: ServiceInterop.dll generates query plans natively → SDK handles aggregation internally → bug masked
  • Linux: No ServiceInterop → SDK calls our DefaultQueryPlanStrategy → both issues surface

Changes

  • src/CosmosDB.InMemoryEmulator/InMemoryContainer.cs — Evaluate literal/expression fields in ProjectAggregateFields
  • src/CosmosDB.InMemoryEmulator/DefaultQueryPlanStrategy.cs — Bypass SDK aggregate pipeline when literals appear alongside aggregates
  • tests/.../Issue67StringLiteralAliasTests.cs — 5 integration tests (string, numeric, boolean, null literals + multi-query)
  • .github/workflows/_build-and-test.yml — Add integration-windows job + rename existing job to integration-linux
  • src/Directory.Build.props — Version bump to 4.0.19

CI Improvement

This bug was platform-specific and only caught when the release workflow ran on Linux. To prevent this class of bug in future, integration tests now run on both ubuntu-latest and windows-latest in CI, ensuring both ServiceInterop and non-ServiceInterop code paths are exercised on every PR.

Testing

  • All 307 integration tests pass (Linux)
  • All 7701 unit tests pass
  • Prerelease v4.0.19-beta.1 published and verified

McNultyyy and others added 2 commits May 18, 2026 23:03
… round-trip (#64)

ExprToString did not wrap TernaryExpression or CoalesceExpression in
parentheses when they appeared as operands of higher-precedence binary,
unary, BETWEEN, IN, or LIKE operators. When the SDK's transformed query
was round-tripped through SimplifySdkQuery → re-parse, the missing
parentheses produced a different AST — causing COUNT(expr) to evaluate
the wrong condition and miscount documents.

Added WrapIfLowPrecedence helper that wraps ternary/coalesce nodes in
parens when they appear in operator positions that would otherwise
re-parse with different associativity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents how to publish beta/prerelease and stable packages via the
existing release.yml workflow (tag push convention) for AI/LLM agent
discoverability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@McNultyyy McNultyyy requested a review from lemonlion as a code owner May 19, 2026 18:09
@McNultyyy McNultyyy marked this pull request as draft May 19, 2026 18:11
@McNultyyy McNultyyy force-pushed the mcnultyyy/fix-string-literal-aliases-linux branch from 8fb346e to 2313249 Compare May 19, 2026 18:14
@McNultyyy McNultyyy changed the title fix: evaluate literal expressions in ProjectAggregateFields (#67) [DO NOT MERGE] fix: evaluate literal expressions in ProjectAggregateFields (#67) May 19, 2026
@McNultyyy McNultyyy changed the base branch from mcnultyyy/fix-count-ternary-parenthesization to main May 19, 2026 18:15
String literal aliases (e.g. SELECT 'Settlement' AS Label, COUNT(1) ...)
returned null on Linux because ProjectAggregateFields treated them as
property paths via SelectToken. On Windows the bug was masked by
ServiceInterop's native aggregate pipeline.

The fix checks for non-identifier SqlExpr nodes in the else branch and
evaluates them via EvaluateSqlExpression, matching the existing pattern
used in the GROUP BY aggregate path.

Covers string, numeric, boolean, and null literal aliases with
integration tests.

Bumps version to 4.0.19.

Closes #67

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@McNultyyy McNultyyy force-pushed the mcnultyyy/fix-string-literal-aliases-linux branch from 2313249 to 520abfb Compare May 19, 2026 18:36
McNultyyy and others added 2 commits May 19, 2026 20:24
…gences

ServiceInterop.dll is only available on Windows, causing fundamentally
different code paths to execute. Adding a windows-latest integration
test job ensures both paths are tested on every PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@McNultyyy McNultyyy changed the title [DO NOT MERGE] fix: evaluate literal expressions in ProjectAggregateFields (#67) Fix string literal aliases in aggregate queries returning null on Linux + add cross-platform CI May 19, 2026
@McNultyyy McNultyyy changed the title Fix string literal aliases in aggregate queries returning null on Linux + add cross-platform CI [DO NOT MERGE] Fix string literal aliases in aggregate queries returning null on Linux + add cross-platform CI May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

String literal aliases in aggregate queries return null on Linux

1 participant