Skip to content

feat: add FSharp.Azure.Cosmos.Sql, a syntax tree, printer, validator and function catalog for Cosmos DB SQL - #42

Open
xperiandri wants to merge 7 commits into
mainfrom
feat/cosmos-sql-spec-model
Open

xperiandri wants to merge 7 commits into
mainfrom
feat/cosmos-sql-spec-model

Conversation

@xperiandri

@xperiandri xperiandri commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed Changes

Depends on #48. The project FSharp.Azure.Cosmos.Sql with EquatableArray and the ImmutableArray patterns is split off into #48, as asked in the review, and this branch is built on it. Until #48 is merged, this pull request also shows its commit; the model is the six commits after it. It targets main and not the branch of #48, because the build workflow runs only for pull requests into main.

Queries in this library are SQL strings today (QueryDefinition) or go through the SDK's LINQ provider. A string is only checked when the service runs it. A reserved word used as a property name (c.value), a parameter where the service accepts none, TOP inside a subquery or an unknown function all fail at run time, and text built by concatenation cannot be inspected, rewritten or compared. The SDK has a typed model of the query language (its SqlObjects classes, the sql.g4 grammar and its text printer), but all of it is internal. The query translator for F# quotations planned in ADR 0001 (#34) needs a model this repository owns: a query is built as data, checked, and then printed the same way every time.

This pull request adds that model as a new package, FSharp.Azure.Cosmos.Sql. It targets net10.0 and depends on FSharp.Core only (System.Text.Json is in-box).

  • Syntax tree. The query is F# records and unions instead of text, one case per construct of the SDK grammar, named after the SDK's classes (SqlQuery, ScalarExpression, CollectionExpression, …). There is no case for raw SQL text. Child lists are EquatableArray<'T> (feat: add FSharp.Azure.Cosmos.Sql with EquatableArray and the ImmutableArray patterns #48), an immutable array that compares its elements. Plain ImmutableArray<'T> compares array references, which would break the equality of whole trees that tests and a query cache rely on.
  • Printer. It writes a tree as query text following the SDK's printer rules: every property access as c["name"] (safe for reserved words), every operator in parentheses, the SDK's string escaping, and numbers in the invariant culture. It has two modes. The parameterized mode keeps parameters as @name. The inline mode writes each parameter's value as a literal, for APIs that take query text without parameters, such as the filter predicate of a patch.
  • Validator. SqlQuery.validate reports the rules the grammar cannot express, as error codes with English messages. Examples: SELECT * over a JOIN, TOP in a subquery, an ORDER BY key that is neither a property path nor VectorDistance or a scoring function, a reserved word as an alias, an empty IN list, a wrong number of function arguments.
  • Function catalog. The built-in functions are data (Catalog.all, and the case-insensitive Catalog.byName): every name the SDK knows plus the documented names it lacks, 174 in all. 52 are specified from their Microsoft Learn pages (argument count, result type, index use, where they may appear); the others are marked unsupported with a reason. Catalog.call builds a call after checking the function and the number of arguments.
open FSharp.Azure.Cosmos.Sql

let c = ScalarExpression.PropertyRef (Identifier "c")
let field name = ScalarExpression.MemberIndexer (c, ScalarExpression.Literal (Literal.String name))
let param name = ScalarExpression.ParameterRef (ParameterName name)

let query = {
    Select = SelectSpec.Value c
    Distinct = false
    Top = ValueSome (SpecValue.Parameter (ParameterName "@take"))
    From = ValueSome (CollectionExpression.Aliased (Collection.InputPath (Identifier "c", ValueNone), ValueNone))
    Where = ValueSome (ScalarExpression.Binary (BinaryOperator.GreaterThanOrEqual, field "age", param "@minAge"))
    GroupBy = EquatableArray.empty
    OrderBy = EquatableArray.singleton { Expression = field "name"; Order = ValueNone }
    OrderByRank = false
    OffsetLimit = ValueNone
}

SqlQuery.validate query                    // empty: the query breaks no rule
Printer.print PropertyStyle.Brackets query
// SELECT TOP @take VALUE c FROM c WHERE (c["age"] >= @minAge) ORDER BY c["name"]

Nobody is expected to write trees by hand like this; the quotation translator will build them. The package stands on its own so that the translator, its tests and later tools share one model.

Not in this pull request (later steps of ADR 0001):

  • translating F# quotations;
  • parsing SQL text into the tree;
  • rewriting passes such as simplification;
  • the validator rules that need emulator checks first (ORDER BY and OFFSET LIMIT inside subqueries, GROUP BY projections);
  • conformance tests against the SDK's ANTLR grammar;
  • property-based tests.

Catalog entries stay unsupported until a translator needs them.

Deliberate differences from the SDK or the ADR text

  • The printer writes single spaces where the SDK writes NOT LIKE and a space after GROUP BY. The text equals the SDK baselines after whitespace normalization.
  • / in strings is not escaped. The ADR lists \/, but the SDK never writes it: it escapes only the control characters, " and \, and its parser baseline prints "\/solidus" as "/solidus".
  • Object keys are escaped like other strings. The SDK writes them raw, which gives the same text for every key that needs no escaping.
  • The validator refuses a parameter as an ORDER BY key, but accepts parameters as arguments of VectorDistance and the scoring functions. Microsoft Learn and the SDK's query-plan baseline pass the query vector that way: ORDER BY VectorDistance(c.embedding, @embedding).

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)

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

Six commits on top of #48: syntax tree, printer, catalog, validator, packaging and the solution tree of the agent instructions. Each builds and passes its tests on its own. The changelog lists the package under Unreleased, and the packaging commit removes the IsPackable=false that #48 sets.

tests/Cosmos.Sql.Tests has 75 tests, 15 of them from #48, and needs no emulator:

  • the equality of whole syntax trees: separately built trees are equal, hash alike and find each other as dictionary keys;
  • printer output for every kind of node, mostly with expected strings taken from the SDK's own baseline files (cited in the tests);
  • escaping of every control character;
  • printing under fr-FR and under a culture with unusual number symbols;
  • validator accept and reject cases, one rule at a time;
  • argument counts in the catalog;
  • a comparison of the catalog with the function names of the SDK version in use, so that the test fails when an SDK update adds or removes a function.

Verification:

  • dotnet build FSharp.Azure.Cosmos.slnx in Debug and Release: 0 errors and the same warnings as main. With -p:WarnOn=3390, which reports malformed documentation comments, the two new projects give no warning.
  • New tests: 75 passed in Debug and in Release, and every commit passes the tests it has (17, 53, 61, 75, 75, 75).
  • Strict documentation build (fsdocs build --strict with the arguments of build.cmd BuildDocs): passed. Fantomas check: clean.
  • dotnet pack: the package depends on FSharp.Core only and contains the XML documentation and THIRD-PARTY-NOTICES.md, which attributes the material taken from the Azure Cosmos DB .NET SDK (MIT).
  • The example above was compiled and run against the built package; it prints exactly the line shown.

Changes after the review

The branch was rebuilt on #48 with every change folded into the commit it belongs to; the review threads are answered one by one.

  • Split: EquatableArray.fs, ImmutableArrayPatterns.fs, the two projects and their tests (15 by now) are feat: add FSharp.Azure.Cosmos.Sql with EquatableArray and the ImmutableArray patterns #48. The two tests that compare whole syntax trees stay here as SyntaxTests, because they need the tree.
  • Plain documentation comments: a comment whose <summary> holds no tag is plain /// lines, 35 comments in 8 files. The generated XML documentation is the same before and after, member by member.
  • One-line summaries in Syntax.fs only: the summary of a union case is on one line when the line is at most 60 characters long, 33 cases: the operators, the sort orders and the other cases with a very short text.
  • ValidationError is a struct, and ValidationOptions.Default is one instance held in a static let.
  • CatalogData.functions is a seq { } piped into ImmutableArray.CreateRange, and Keywords.reserved is built with |> _.ToFrozenSet(...).
  • The members of the validator have documentation comments.
  • The test categories are sorted by name.
  • Numbers in Inline.ofJsonElement (Copilot's remark on test: add Cosmos.Sql.Hedgehog with the generator helpers #54): the comment promised a constant that writes the same JSON, which does not hold for a number with more digits than a double has, such as 0.123456789012345678901. The conversion stays: Azure Cosmos DB keeps every number as an IEEE 754 double, so the nearest double is the value the service reads from those digits too. The comment now says that, and a test fixes the rounding.
  • One value per parameter in the inline mode (Copilot's remark on test(benchmarks): measure what a quotation query costs on the client #56): the inline pass asked its encoder once for every use of a parameter, so an encoder that reads a clock or serializes a value could put two different literals for one parameter into a query. It now asks once for every parameter name and uses that answer everywhere, as a named parameter of a QueryDefinition has one value.
  • Arrays in the tests (review of 2026-10-11): the tree builders of the tests, call, udf, arrayOf and objectOf in Ast.fs, take an array and wrap it with EquatableArray.unsafeOfArray, and the test bodies pass array literals where they passed lists.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 5, 2026 23:15

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@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.

Details

Comment thread src/Cosmos.Sql/FunctionSpec.fs Outdated
Comment thread src/Cosmos.Sql/Keywords.fs Outdated
Comment thread src/Cosmos.Sql/ValidationError.fs Outdated
Comment thread src/Cosmos.Sql/ValidationError.fs Outdated
Comment thread tests/Cosmos.Sql.Tests/Ast.fs Outdated
Comment thread tests/Cosmos.Sql.Tests/TestCategories.fs
Comment thread src/Cosmos.Sql/Validator.fs
Comment thread src/Cosmos.Sql/Syntax.fs Outdated
Comment thread src/Cosmos.Sql/CatalogData.fs Outdated
@xperiandri xperiandri changed the title feat: add FSharp.Azure.Cosmos.Sql, a syntax tree, printer, validator and function catalog for Cosmos DB SQL feat: add FSharp.Azure.Cosmos.Sql, a syntax tree, printer, validator and function catalog for Cosmos DB SQL Oct 10, 2026
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch from e075d78 to ab6ee05 Compare October 10, 2026 13:21
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; Odd and Even stay inside their range; DateOnly
and TimeOnly get generators, upstream through
hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch from ab6ee05 to 7ffaf64 Compare October 10, 2026 16:23
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code
style is answered, with formatting and nullness checking still open.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, Pascal-case
assertion functions and ImmutableArray for shared test data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch 3 times, most recently from 58a3c24 to 662cffd Compare October 11, 2026 09:03
xperiandri added a commit that referenced this pull request Oct 11, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

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

@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.

use unsafeOfArray in tests to create instances

@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch 2 times, most recently from a058975 to 7b965ba Compare October 11, 2026 10:11
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The maintainer's reviews of 2026-10-10 changed several things the record
describes, and it now says so where each belongs.

The Hedgehog MSTest adapter (#43): the in-repository copy follows the
coding guidelines of this repository, while the upstream pull request
keeps upstream's style in a copy of its own, so a change of behaviour
goes into both; an invocation that MSTest could not run stops the run
instead of being shrunk; the cancellation token is checked after every
invocation, whatever its outcome, which the automatic review of #50
asked for; Odd and Even stay inside their range; DateOnly and TimeOnly
get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The
open question of the adapter's code style is answered, formatting and
nullness checking included: the two projects are formatted with Fantomas
and build with nullness checking on, and only the verbatim port keeps
upstream's settings.

Collections (section 4): the shared collection functions of #47 that
return voption and struct tuples, tested with properties in #50, and the
rule of one module per pipeline of #49.

Build (section 5): why FAKE's DotNet.test cannot run the test
applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP.

The spec model (sections 6 and 16): EquatableArray and the ImmutableArray
patterns are #48 and the model itself #42; the validator returns an
ImmutableArray of struct errors.

Golden baselines (section 13): a literal type provider was evaluated on
the question asked in #46 and rejected, with the reasons.

Tests (section 22): test category attributes sorted by name, in #51
with an instruction scoped to those files, Pascal-case assertion
functions and ImmutableArray for shared test data.

Two more answers of the same day: the Windows emulator lane of #45 runs
weekly instead of nightly (section 2), and the shared collection
functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the
global one (section 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch from 7b965ba to 97e83f5 Compare October 11, 2026 11:09
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

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

xperiandri commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

use unsafeOfArray in tests to create instances

Done, in the commits that introduce the tests (c6fd556, 8786bbf, 2464182) and in #48 (2cc6691):

  • The tree builders of Ast.fs – call, udf, arrayOf and objectOf – take an array and wrap it with EquatableArray.unsafeOfArray, and so does orderedBy in ValidatorTests.fs. Their call sites pass array literals: call "ARRAY_CONTAINS" [| c |> prop "tags"; param "@p0" |].
  • Every EquatableArray.ofSeq [ … ] in a test body is EquatableArray.unsafeOfArray [| … |] now.
  • The tests of EquatableArray itself (feat: add FSharp.Azure.Cosmos.Sql with EquatableArray and the ImmutableArray patterns #48) wrap their array literals too. ofArray and ofSeq are left only in the test that checks that they copy.
  • EquatableArray.singleton and EquatableArray.empty stay as they are: neither copies anything.

The generators of #55 wrap the arrays they generate in the same way.

The branch is also rebased onto main with #53.

xperiandri and others added 2 commits October 11, 2026 13:46
…mutableArray` patterns

Starts src/Cosmos.Sql (FSharp.Azure.Cosmos.Sql, net10.0, FSharp.Core
only), the project of the Cosmos DB SQL model of ADR 0001 (sections 4, 6
and 15), with the two collection helpers that the syntax tree is built
from, so that they can be reviewed on their own before the tree, the
printer and the validator follow.

EquatableArray<'T> is a struct over ImmutableArray<'T> with sequence
equality and a combined hash code. ImmutableArray's own Equals and
EqualityComparer<T>.Default compare the reference of the wrapped array,
which a struct tuple, a default-comparer HashSet or a C# caller would
see, and a default array never equals an empty one. The nodes of the
syntax tree will hold their children in this wrapper, so that their
equality is structural under every comparer. The EquatableArray module
creates and maps the arrays; its unsafeOfArray wraps an array without
copying it, as ImmutableCollectionsMarshal.AsImmutableArray does, for a
caller that gives the array away. Implicit conversions to and from
ImmutableArray wrap and unwrap without copying; F# applies them without
a warning at method arguments, and with warning FS3391 elsewhere.

The struct partial active patterns Arr0, Arr1, Arr2, Arr3 and ArrN match
an ImmutableArray by its length without allocating: each returns a
struct value option or a bool, and its payload is a struct tuple.

tests/Cosmos.Sql.Tests is the new emulator-free MSTest project, with 15
tests under the category Collections: a file for the struct, one for the
module and one for the patterns. Both projects join the solution and the
solution filter, and the agent instructions list them. The library is
not packed yet (IsPackable is false): a push to main publishes every
packable project to GitHub Packages, and the collection types alone are
no package. The commit that gives the package its metadata removes the
setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the syntax tree of the Cosmos DB NoSQL query language to
FSharp.Azure.Cosmos.Sql: one union case or record per non-terminal of the SDK
grammar sql.g4, named after the SDK's SqlObjects classes (ADR 0001 sections 6
and 15). There is no case for raw text and no shift operator; integer literals
are int64 and numbers keep the SDK's integer/double split.

Child sequences are EquatableArray<'T>, so the equality of the tree is
structural under every comparer. SyntaxTests checks it on whole trees:
separately built trees with equal children are equal, hash alike and find each
other as dictionary keys, which the plan cache needs.

Keywords holds the reserved words of the grammar and its identifier rule.
THIRD-PARTY-NOTICES.md attributes the material taken from the Azure Cosmos DB
.NET SDK (MIT) and joins the solution items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch from 97e83f5 to bebed8e Compare October 11, 2026 11:50
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri and others added 5 commits October 11, 2026 14:56
Printer writes the syntax tree as query text following the rules of the SDK's
SqlObjectTextSerializer without pretty printing (ADR 0001 section 6): bare
aliases; property accesses as alias["name"] with JSON escaping, or alias.name
under PropertyStyle.DotWhenSafe when the name is a safe identifier; every
binary, unary, conditional, coalesce, IN, BETWEEN and LIKE expression in
parentheses; the SDK's string escaping (the solidus stays as is, like in the
SDK); integers in full and other numbers with "R", both in the invariant
culture; lower-case literal keywords; the udf. prefix; the clause order of the
grammar. The text equals the SDK baselines after whitespace normalization; it
differs only where the SDK writes "NOT  LIKE" and a space after GROUP BY.

Printing is total and has two modes: print writes parameters as @name, while
printInline and printPredicateInline first run Inline.substituteParameters,
which replaces every parameter, in every clause and subquery, with the
constant an encoder gives for it. The encoder contract is one function from a
parameter name to the literal, if any; Inline.ofJsonElement turns a value
written by a serializer into such a literal. printPredicate writes the
"FROM c WHERE ..." text that the filter predicate of a patch takes. Errors of
the substitution are ValidationError values.

The golden tests cover every node kind and cite the SDK baselines they
reproduce (SqlObjectVisitorBaselineTests, the parser baselines and the LINQ
baselines of SDK 3.62.0), escaping of every control character, and culture
independence under fr-FR and a culture with other number symbols.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built-in functions of the query language are data (ADR 0001 section 6): a
FunctionSpec per name with category, arity, return kind, undefined rule,
index usage, availability, determinism, the ORDER BY and ORDER BY RANK flags,
the Learn page and a status. CatalogData.fs is the data file; Catalog exposes
it as Catalog.all, Catalog.byName (a FrozenDictionary with OrdinalIgnoreCase,
since function names are case-insensitive), Catalog.tryFind and Catalog.call,
which builds a call with the catalog's spelling after checking that the
function is known, specified and given an accepted number of arguments.

The catalog is seeded with every name of SqlFunctionCallScalarExpression.Names
of SDK 3.62.0 except the internal ones that start with an underscore, plus the
documented names the SDK does not list: the INTBIT* spellings, NOW, AGO,
NUMBERBIN, REGEXREPLACE, REGEXREPLACEALL, ST_AREA and the GETCURRENT*STATIC
functions. The functions the first translator slice needs are specified from
their Learn pages (type checks, the string and array functions, aggregates,
IIF, the integer functions, basic date and time functions, and the scoring
functions VectorDistance, FullTextScore and RRF); every other entry is
Unsupported with its reason: not specified yet, undocumented, a keyword of
the grammar or the SDK's spelling of a documented name.

A test reads the names of the SDK in use by reflection and fails when an SDK
update adds or removes one; the test project references Microsoft.Azure.Cosmos
for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SqlQuery.validate and SqlQuery.validateWith check a query and its subqueries
against the rules of the query language that the grammar cannot express and
return them as ImmutableArray<ValidationError>, each with a code and an
English message that quotes the offending fragment (ADR 0001 section 6). The
rules are those the first translator slice needs:

- SELECT * only with exactly one collection in the FROM clause;
- TOP never in a subquery;
- no parameter in a GROUP BY key or as an ORDER BY key, while the arguments
  of a function allowed in ORDER BY or ORDER BY RANK may be parameters, as
  the vector and hybrid search queries on Microsoft Learn and the SDK's
  query plan baseline pass the query vector; parameter names of the form
  @name;
- ORDER BY items only property paths or calls of functions the catalog
  allows in ORDER BY; ORDER BY RANK items only scoring functions; functions
  allowed only in ORDER BY RANK nowhere else;
- identifiers of the form [A-Za-z_][A-Za-z_0-9]* that are not reserved words;
- non-empty IN lists and select lists;
- built-in functions known to the catalog and called with an accepted arity;
- finite number literals, integer literals within -2^53..2^53 unless
  ValidationOptions.AllowLossyInt64 is set, non-negative counts.

OFFSET and LIMIT together, counts as literals or parameters, string object
keys and the separation of plain and rank ORDER BY items hold by construction
of the syntax tree. The documentation of the tree, the keywords, the printer
and the catalog now refers to the rules the validator checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gives the new package its NuGet metadata and packs THIRD-PARTY-NOTICES.md
next to README.md and LICENSE, because the package contains material derived
from the Azure Cosmos DB .NET SDK (ADR 0001 section 5). The package depends on
FSharp.Core only; System.Text.Json is in-box on net10.0. IsPackable=false,
which kept the library out of the packages while it held the collection types
only, is removed.

The project is already part of the solution filter that FAKE packs, the test
glob tests/**/*.Tests.??proj picks up the new test project, the coverage
assembly filter "-*.Tests;-*.Tests.Infrastructure" excludes it while keeping
the library, and fsdocs builds the API documentation of every src project.
The changelog lists the package under Unreleased.

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

The solution tree of the agent instructions names every file of
src/Cosmos.Sql with what it holds, in compile order, as it does for
src/Cosmos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/cosmos-sql-spec-model branch from bebed8e to 82913d4 Compare October 11, 2026 13:07
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
The generators of the SQL model and the printer laws need both: the
syntax tree, printer and validator of #42 and the property attribute of
#43. This merge is only the base of the stacked branch; it goes away
when both pull requests are in main and the branch is rebased onto it.

THIRD-PARTY-NOTICES.md is added by both; the result keeps the text of
#42 and appends the fsharp-hedgehog section of #43.

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