Skip to content

test: record how the emulator evaluates the queries the translator needs - #44

Open
xperiandri wants to merge 3 commits into
mainfrom
test/query-semantics
Open

xperiandri wants to merge 3 commits into
mainfrom
test/query-semantics

Conversation

@xperiandri

@xperiandri xperiandri commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed Changes

The planned translation of F# quotations into Cosmos DB SQL (ADR 0001, #34) has rules that depend on how Cosmos DB evaluates certain queries, and the documentation does not settle all of them. For example: can c.v = @p be true for a document where v is missing or null? Does ?? replace null? May a subquery contain TOP? This pull request adds tests that ask the emulator these questions and assert its exact answers. The translator's rules can then be written from facts, and a change in the engine shows up as a failing test.

tests/Cosmos.Tests/QuerySemanticsTests.fs (category QuerySemantics, 22 test methods, 52 cases with their data rows). Each test seeds the documents it needs as raw JSON into its own database, so a property can be missing, null or of another type. It then runs the query in one logical partition through the stream iterator and compares the raw JSON answer. No serializer sits in between, so a member missing from the answer means the engine returned undefined.

The Windows emulator (local) and the Linux vNext emulator (CI and the dev container) answered alike, except where noted:

  • Equality. =, != and IN are false or true between different JSON types, null included, and undefined only when the value is missing. So c.v = @p never matches a missing or null value, while NOT (c.v = @p) matches null but not a missing value.
  • Ordering. < and >= are undefined between different types and for arrays and objects.
  • Coalesce. ?? replaces only a missing value: c.v ?? "default" stays null when v is null.
  • Integer division. INTDIV and INTMOD truncate toward zero, like F# / and % on integers. A zero divisor or a fractional operand on either side gives undefined, where F# throws.
  • Division by zero. 7 / 0 compares as infinity in a filter. A query that returns it fails with 400, because JSON has no infinity. The Windows emulator reports substatus 4001, the vNext emulator 0.
  • Parameters. A parameter works as the ignore-case flag of STRINGEQUALS, CONTAINS, STARTSWITH and ENDSWITH, as the whole filter (WHERE @p0), and as an indexer (c.m[@key], c.arr[@index]).
  • STRINGEQUALS is undefined unless both operands are strings.
  • Case folding. On ten tricky pairs, the ignore-case flag of the Windows emulator agrees with .NET OrdinalIgnoreCase except for the Greek final sigma: ς against Σ is not equal, while .NET calls them equal. The vNext emulator also equates the dotted capital I, the Kelvin sign and the Angstrom sign with the letters they lowercase to.
  • Objects and arrays. Object equality ignores the order of properties; array equality does not.
  • Subqueries. TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery (errors SC2203, SC2202 and SC2204). DISTINCT, GROUP BY, nested subqueries, and a filter, aggregate, DISTINCT, TOP, ORDER BY or OFFSET LIMIT outside a FROM subquery all run. A filter outside a GROUP BY subquery stands in for the missing HAVING. An ORDER BY outside a subquery sorts only by paths of the documents, not by a value the subquery computes.
  • Sources of IN. The source must be a path or a subquery. kv IN ObjectToArray(c.m) and t IN [1, 2] are syntax errors, while kv IN (SELECT VALUE ObjectToArray(c.m)) and ARRAY_CONTAINS(ObjectToArray(c.m), {"v": 2}, true) work. The ADR's planned translation of a Map lookup used the invalid form; docs: add ADR 0001 for the FSharp.Azure.Cosmos.Quotations architecture #34 corrects it.
  • ORDER BY keeps documents without the value, first in ascending order, and orders JSON types as undefined, null, booleans, numbers, strings, arrays, objects.
  • ORDER BY over two properties. A composite index (/a ASC, /b ASC) keeps the documents that lack either value: in each key they come first, before null and numbers, and ORDER BY c.a DESC, c.b DESC, the inverse of the index, returns the exact reverse. The Windows emulator refuses ORDER BY c.a, c.b DESC, which no composite index serves, with 400 (does not have a corresponding composite index); the vNext emulator, which has no composite indexes yet, sorts it anyway.
  • Spatial functions. With GeoJSON passed as raw JSON parameters, the form planned for captured geometry values, the Windows emulator measures ST_DISTANCE and ST_AREA in metres on the WGS-84 ellipsoid (110 574 m for one degree of latitude at the equator), tests points against a polygon with ST_INTERSECTS, ST_WITHIN and ST_ISVALID, explains an invalid point with ST_ISVALIDDETAILED, and filters stored points with ST_DISTANCE and ST_WITHIN. The vNext emulator fails a spatial function in the projection with 500, This query type isn't supported yet, and answers a spatial filter without documents, so the SDK throws while it reads the page.

The five tests whose answers differ state both. They tell the emulators apart through Emulator.readKindAsync, added to tests/Cosmos.Tests.Infrastructure: the Windows emulator answers an unauthenticated request to its root with 401, the vNext emulator with a success status, which the CI setup of the emulator already relies on.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Tests only; the package is unchanged.

Checklist

  • Build and tests pass locally
  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have added necessary documentation (if appropriate)

Further comments

  • Windows emulator: Debug 242/242 and Release 242/242 (the 190 tests of main plus the 52 new ones). The vNext emulator in CI runs the same tests. Fantomas is clean.
  • The vNext emulator lays out the JSON of its errors with indentation, so a rejection is recognised by its quoted error code alone.
  • ADR 0001 (docs: add ADR 0001 for the FSharp.Azure.Cosmos.Quotations architecture #34) records these facts as decision records, with the two rules that change: the re-nesting table of subqueries and the ObjectToArray lowering.

Changes after the review

Every change is folded into the commit it belongs to; the review threads are answered one by one.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 10, 2026 00:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Two stated integer-division and substatus behaviors are not fully asserted by the tests.

1 open finding
What changed in this PR

Adds emulator-backed integration tests that establish Cosmos DB query semantics needed by the planned quotation translator.

Changes:

  • Adds 36 query-semantics cases across 17 test methods.
  • Covers comparisons, operators, parameters, functions, subqueries, and ordering.
  • Introduces a dedicated test category.
File Description
TestCategories.fs Adds the query-semantics category.
QuerySemanticsTests.fs Adds emulator query behavior tests and helpers.
FSharp.Azure.Cosmos.Tests.fsproj Includes the new test file.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/Cosmos.Tests/QuerySemanticsTests.fs Outdated
@xperiandri
xperiandri force-pushed the test/query-semantics branch 4 times, most recently from c2c4ffb to dcd5272 Compare October 10, 2026 00:55
xperiandri added a commit that referenced this pull request Oct 10, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a nightly,
non-blocking Windows lane for this.

windows-emulator-nightly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator every night and on demand. It also
runs for a pull request that changes the lane itself, so that a change
to it is checked before it merges, and for no other pull request, so it
holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, and a parameter as the ignore-case
flag is planned from its value. Ignoring case is an expanded index scan,
not the seek the STRINGEQUALS page states, and LOWER and UPPER cost the
same there although the documentation calls them a full scan. CQ1019 is
withdrawn, and so is CQ1002, because ORDER BY keeps the documents
without the sort value, by one property and, as #44 now shows, by two
over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the test/query-semantics branch 2 times, most recently from 1750212 to 4235945 Compare October 10, 2026 08:19
xperiandri added a commit that referenced this pull request Oct 10, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, EXISTS over an array of objects
costs what a partial ARRAY_CONTAINS costs, and a parameter as the
ignore-case flag is planned from its value. Ignoring case is an expanded
index scan, not the seek the STRINGEQUALS page states, and LOWER and
UPPER cost the same there although the documentation calls them a full
scan. CQ1019 is withdrawn, and so is CQ1002, because ORDER BY keeps the
documents without the sort value, by one property and, as #44 now
shows, by two over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri

Copy link
Copy Markdown
Collaborator Author

Division by zero. 7 / 0 compares as infinity in a filter. A query that returns it fails with 400, because JSON has no infinity. The Windows emulator reports substatus 4001, the vNext emulator 0.

Issue to emulator repo

Comment thread tests/Cosmos.Tests/TestCategories.fs
Comment thread tests/Cosmos.Tests/QuerySemanticsTests.fs Outdated
Comment thread tests/Cosmos.Tests/QuerySemanticsTests.fs Outdated
Comment thread tests/Cosmos.Tests/QuerySemanticsTests.fs Outdated
@xperiandri

Copy link
Copy Markdown
Collaborator Author

Issue to emulator repo

The issue for Azure/azure-cosmos-db-emulator-docker is written and open in your browser as a prefilled "Bug report for vNext Emulator", ready to submit:

vNext emulator: a query whose result cannot be represented in JSON fails with x-ms-substatus 0 instead of 4001

It states what the test here pins: SELECT VALUE {"quotient": 7 / 0} FROM c fails with HTTP 400 on both emulators, the Windows emulator with x-ms-substatus: 4001 and the error code 4001 in the body, the vNext emulator with x-ms-substatus: 0. It names the image digest that CI pulled today and refers to Azure/azure-cosmos-db-emulator-docker#310, where the emulator team confirmed that the service answers such a query with HTTP 400 and the code 4001 as well.

I captured the Windows answer in full for it. I could not capture the body of the vNext answer, only its status, the header and the text that the test asserts.

@xperiandri xperiandri left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fix

@xperiandri
xperiandri force-pushed the test/query-semantics branch from eca8ed8 to 2c96276 Compare October 10, 2026 16:23
xperiandri added a commit that referenced this pull request Oct 10, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, EXISTS over an array of objects
costs what a partial ARRAY_CONTAINS costs, and a parameter as the
ignore-case flag is planned from its value. Ignoring case is an expanded
index scan, not the seek the STRINGEQUALS page states, and LOWER and
UPPER cost the same there although the documentation calls them a full
scan. CQ1019 is withdrawn, and so is CQ1002, because ORDER BY keeps the
documents without the sort value, by one property and, as #44 now
shows, by two over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a nightly,
non-blocking Windows lane for this.

windows-emulator-nightly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator every night and on demand. It also
runs for a pull request that changes the lane itself, so that a change
to it is checked before it merges, and for no other pull request, so it
holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The attributes of tests/Cosmos.Tests/TestCategories.fs were in the order
of the string constants they once replaced, with later ones appended.
They are sorted by type name now, as the review of #44 asks.

A path-scoped instruction keeps them sorted:
.github/instructions/test-categories.instructions.md applies to every
file named TestCategories.fs for GitHub Copilot, and
.claude/rules/test-categories.md imports it for Claude Code with the
same path pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a non-blocking
Windows lane for this. It planned a nightly one; the review of this
pull request found that too often, so the lane is weekly.

windows-emulator-weekly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator early every Monday and on demand.
It also
runs for a pull request that changes the lane itself, so that a change
to it is checked before it merges, and for no other pull request, so it
holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a non-blocking
Windows lane for this. It planned a nightly one; the review of this
pull request found that too often, so the lane is weekly.

windows-emulator-weekly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator early every Monday and on demand.
It also runs for a pull request that changes the lane itself, so that a
change to it is checked before it merges, and for no other pull
request, so it holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a non-blocking
Windows lane for this. It planned a nightly one; the review of this
pull request found that too often, so the lane is weekly.

windows-emulator-weekly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator early every Monday and on demand.
It also runs for a pull request that changes the lane itself, so that a
change to it is checked before it merges, and for no other pull
request, so it holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, EXISTS over an array of objects
costs what a partial ARRAY_CONTAINS costs, and a parameter as the
ignore-case flag is planned from its value. Ignoring case is an expanded
index scan, not the seek the STRINGEQUALS page states, and LOWER and
UPPER cost the same there although the documentation calls them a full
scan. CQ1019 is withdrawn, and so is CQ1002, because ORDER BY keeps the
documents without the sort value, by one property and, as #44 now
shows, by two over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the test/query-semantics branch from 2c96276 to 47cc4a1 Compare October 10, 2026 21:15
xperiandri added a commit that referenced this pull request Oct 10, 2026
The attributes of tests/Cosmos.Tests/TestCategories.fs were in the order
of the string constants they once replaced, with later ones appended.
They are sorted by type name now, as the review of #44 asks.

A path-scoped instruction keeps them sorted:
.github/instructions/test-categories.instructions.md applies to every
file named TestCategories.fs for GitHub Copilot, and
.claude/rules/test-categories.md imports it for Claude Code with the
same path pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a non-blocking
Windows lane for this. It planned a nightly one; the review of this
pull request found that too often, so the lane is weekly.

windows-emulator-weekly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator early every Monday and on demand.
It also runs for a pull request that changes the lane itself, so that a
change to it is checked before it merges, and for no other pull
request, so it holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, EXISTS over an array of objects
costs what a partial ARRAY_CONTAINS costs, and a parameter as the
ignore-case flag is planned from its value. Ignoring case is an expanded
index scan, not the seek the STRINGEQUALS page states, and LOWER and
UPPER cost the same there although the documentation calls them a full
scan. CQ1019 is withdrawn, and so is CQ1002, because ORDER BY keeps the
documents without the sort value, by one property and, as #44 now
shows, by two over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the test/query-semantics branch from 47cc4a1 to e0db2c5 Compare October 11, 2026 09:06
xperiandri and others added 3 commits October 11, 2026 12:42
The quotation translator planned in ADR 0001 turns F# expressions into
Cosmos DB SQL, and several of its rules depend on behaviour the
documentation leaves open: what a comparison with a missing or null
value returns, what ?? does with null, how INTDIV rounds, and whether a
subquery may hold TOP, ORDER BY or OFFSET LIMIT. QuerySemanticsTests
asks the emulator and asserts its exact answers, so that those rules are
written from facts and a change in the engine shows up as a failing
test.

Each test seeds the documents it needs as raw JSON into a database of
its own and reads the answer through the stream iterator in one logical
partition, so no serializer sits between the engine and the assertion
and a member the answer lacks is undefined. The 18 test methods (43
cases with their data rows) cover equality and ordering across JSON
types, comparisons with a parameter and their negation, the coalesce
operator, INTDIV and INTMOD, division by zero, a parameter as the
ignore-case flag of string functions, as the whole filter and as an
indexer, STRINGEQUALS over non-strings, case folding against .NET
OrdinalIgnoreCase, object and array equality, ObjectToArray, accepted
and rejected subquery shapes (a filter outside a GROUP BY subquery
included, which emulates HAVING), ORDER BY over values a subquery
computes, the sources IN accepts, and ORDER BY over missing values and
mixed types.

The Windows emulator and the Linux vNext emulator that CI runs give
the same answers, except that the vNext emulator's ignore-case flag
also equates a few letters with their lowercase forms, and that it
reports substatus 0 where the Windows emulator reports 4001 for a
returned infinity. The tests concerned state both, telling the
emulators apart through the new Emulator.readKindAsync: the Windows
emulator answers an unauthenticated request to its root with 401, the
vNext emulator with a success status. The vNext emulator also indents
the JSON of its errors, so a rejection is recognised by its quoted
error code alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A composite index keeps the documents that lack one of its values, so an
ORDER BY over two properties returns them, sorted before null and
numbers in each key, and the exact inverse of the index returns the
exact reverse. The Windows emulator refuses directions that no composite
index matches; the vNext emulator has no composite indexes and sorts
without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spatial functions take GeoJSON as raw JSON parameters, the form the
translator plans for captured geometry values. On the Windows emulator
ST_DISTANCE and ST_AREA measure in metres on the WGS-84 ellipsoid,
ST_INTERSECTS, ST_WITHIN and ST_ISVALID test points against a polygon,
ST_ISVALIDDETAILED explains an invalid point, and ST_DISTANCE and
ST_WITHIN filter stored points. The vNext emulator fails a spatial
function in the projection with 500, "This query type isn't supported
yet", and answers a spatial filter without documents, so the SDK throws
while it reads the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
#44 asks the Windows and the vNext emulator how they evaluate the
constructs the translator relies on. The record now states their
answers where it waited for them: the comparison table behind guard
elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero,
a returned infinity failing the query, a parameter as case flag,
predicate and indexer, STRINGEQUALS over non-strings, case folding per
emulator, object equality without property order, and ORDER BY over
missing values. A Phase 0 result paragraph lists what is settled and
what is still open.

Two rules change. The re-nesting table is written from the results:
TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a
filter outside a GROUP BY subquery stands in for HAVING, so a where
over the projected groups is no longer rejected. The ObjectToArray
lowering for map members used kv IN ObjectToArray(...), which is a
syntax error, and now iterates a subquery instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
On the Windows emulator every predicate the translator emits uses the
index: guards keep the seek of the comparison they protect, equality
with an object-valued parameter is a seek, ARRAY_CONTAINS over a
captured array costs what IN costs, EXISTS over an array of objects
costs what a partial ARRAY_CONTAINS costs, and a parameter as the
ignore-case flag is planned from its value. Ignoring case is an expanded
index scan, not the seek the STRINGEQUALS page states, and LOWER and
UPPER cost the same there although the documentation calls them a full
scan. CQ1019 is withdrawn, and so is CQ1002, because ORDER BY keeps the
documents without the sort value, by one property and, as #44 now
shows, by two over a composite index.

The SDK loads Newtonsoft.Json in the CosmosClient constructor whatever
the serializer, and its build check asks every consumer to reference
it, so the Quotations package declares it; whether FSharp.Azure.Cosmos
should declare it instead is a new open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the test/query-semantics branch from e0db2c5 to 1a6217a Compare October 11, 2026 11:03
xperiandri added a commit that referenced this pull request Oct 11, 2026
The pull request workflow runs the integration tests on the Linux vNext
emulator only. The two emulators answer some queries differently, and
tests that state the answers of both (#44) would otherwise check the
Windows half on developer machines alone. ADR 0001 plans a non-blocking
Windows lane for this. It planned a nightly one; the review of this
pull request found that too often, so the lane is weekly.

windows-emulator-weekly runs ./build.cmd DotnetTest in Debug and
Release against the Windows emulator early every Monday and on demand.
It also runs for a pull request that changes the lane itself, so that a
change to it is checked before it merges, and for no other pull
request, so it holds none up.

Its first run showed that the Windows emulator never became ready on
the hosted runners. The start script passed /AllowNetworkAccess, which
the emulator's documentation allows only together with /Key or
/KeyFile, and merely warned when the emulator stayed silent. It now
starts the emulator through its PowerShell module with
Start-CosmosDbEmulator, as the documentation shows for GitHub Actions,
and both Windows scripts fail instead of warning. The build-only
Windows jobs of the pull request workflow never used the emulator, so
they no longer install or start it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants