diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 46978db5..a1d9cf8e 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -12,6 +12,9 @@
```text
/
+├── src/Cosmos.Sql/ – Cosmos DB SQL model (FSharp.Azure.Cosmos.Sql)
+│ ├── EquatableArray.fs – immutable array with structural equality
+│ └── ImmutableArrayPatterns.fs – active patterns that match an `ImmutableArray` by its length
├── src/Cosmos/ – main library (FSharp.Azure.Cosmos)
│ ├── SubStatusCodes.fs – documented Cosmos DB sub-status codes
│ ├── Cosmos.fs – core types and container extensions
@@ -28,6 +31,7 @@
├── src/Shared/ – source files that every F# project under src and tests compiles
│ └── ValueCollections.fs – `Seq`, `List` and `Array` functions that return `voption` and struct tuples
├── tests/Cosmos.Tests/ – MSTest integration test project
+├── tests/Cosmos.Sql.Tests/ – MSTest unit tests of FSharp.Azure.Cosmos.Sql, no emulator needed
├── tests/Cosmos.Tests.Infrastructure/ – shared test fixtures, emulator settings and assertion helpers
├── build/ – FAKE build scripts
└── docsSrc/ – FSharp.Formatting documentation source
diff --git a/FSharp.Azure.Cosmos.slnf b/FSharp.Azure.Cosmos.slnf
index 1a0488cc..c7e70fc3 100644
--- a/FSharp.Azure.Cosmos.slnf
+++ b/FSharp.Azure.Cosmos.slnf
@@ -2,7 +2,9 @@
"solution": {
"path": "FSharp.Azure.Cosmos.slnx",
"projects": [
+ "src\\Cosmos.Sql\\FSharp.Azure.Cosmos.Sql.fsproj",
"src\\Cosmos\\FSharp.Azure.Cosmos.fsproj",
+ "tests\\Cosmos.Sql.Tests\\FSharp.Azure.Cosmos.Sql.Tests.fsproj",
"tests\\Cosmos.Tests.Infrastructure\\FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj",
"tests\\Cosmos.Tests\\FSharp.Azure.Cosmos.Tests.fsproj"
]
diff --git a/FSharp.Azure.Cosmos.slnx b/FSharp.Azure.Cosmos.slnx
index 4ec89bbd..50500575 100644
--- a/FSharp.Azure.Cosmos.slnx
+++ b/FSharp.Azure.Cosmos.slnx
@@ -10,7 +10,9 @@
+
+
diff --git a/src/Cosmos.Sql/AssemblyInfo.fs b/src/Cosmos.Sql/AssemblyInfo.fs
new file mode 100644
index 00000000..a60652e5
--- /dev/null
+++ b/src/Cosmos.Sql/AssemblyInfo.fs
@@ -0,0 +1,23 @@
+// Auto-Generated by FAKE; do not edit
+namespace System
+open System.Reflection
+
+[]
+[]
+[]
+[]
+[]
+[]
+[]
+[]
+do ()
+
+module internal AssemblyVersionInformation =
+ let [] AssemblyTitle = "FSharp.Azure.Cosmos.Sql"
+ let [] AssemblyProduct = "FSharp.Azure.Cosmos"
+ let [] AssemblyVersion = "1.2.0"
+ let [] AssemblyMetadata_ReleaseDate = "2026-10-01T00:00:00.0000000+02:00"
+ let [] AssemblyFileVersion = "1.2.0"
+ let [] AssemblyInformationalVersion = "1.2.0"
+ let [] AssemblyMetadata_ReleaseChannel = "release"
+ let [] AssemblyMetadata_GitHash = "210cf041a0ce44fcc5a5df369550a77a1f75d436"
diff --git a/src/Cosmos.Sql/EquatableArray.fs b/src/Cosmos.Sql/EquatableArray.fs
new file mode 100644
index 00000000..eb17366e
--- /dev/null
+++ b/src/Cosmos.Sql/EquatableArray.fs
@@ -0,0 +1,199 @@
+namespace FSharp.Azure.Cosmos.Sql
+
+open System
+open System.Collections
+open System.Collections.Generic
+open System.Collections.Immutable
+open System.Runtime.InteropServices
+
+///
+/// An immutable array with structural equality: two instances are equal when they hold equal elements in the same
+/// order, and the hash code combines the hash codes of the elements.
+///
+/// The own equality of , its
+///
+/// that calls, compares the reference of the array
+/// it wraps. Only comparers that go through , such as the equality
+/// the F# compiler generates for records and unions, compare its elements;
+/// , a
+/// of arrays or a C# caller compares references, and a default
+/// array never equals an empty one. The nodes of the syntax tree hold their child sequences in this wrapper instead
+/// (the pattern Roslyn incremental generators use), so their equality is structural under every comparer, for
+/// round-trip tests, cache keys and the idempotence of passes.
+///
+///
+/// The default value holds no array; it behaves as an empty array and is equal to one.
+///
+///
+/// The elements, in order.
+[]
+type EquatableArray<'T> (array : ImmutableArray<'T>) =
+
+ ///
+ /// The elements; never a default , so it can be
+ /// enumerated and indexed without a check.
+ ///
+ member _.Items = if array.IsDefault then ImmutableArray<'T>.Empty else array
+
+ /// The number of elements.
+ member _.Length = if array.IsDefault then 0 else array.Length
+
+ /// Whether the array has no elements.
+ member this.IsEmpty = this.Length = 0
+
+ ///
+ /// The element at .
+ ///
+ /// The zero-based position of the element.
+ member this.Item
+ with get (index : int) = this.Items[index]
+
+ ///
+ /// Returns an allocation-free enumerator over the elements, which F# loops use.
+ ///
+ member this.GetEnumerator () = this.Items.GetEnumerator ()
+
+ ///
+ /// Whether holds equal elements in the same order; elements are compared with
+ /// .
+ ///
+ /// The array to compare with.
+ member this.Equals (other : EquatableArray<'T>) =
+ let left = this.Items
+ let right = other.Items
+
+ if left.Length <> right.Length then
+ false
+ else
+ let comparer = EqualityComparer<'T>.Default
+ let mutable equal = true
+ let mutable index = 0
+
+ while equal && index < left.Length do
+ equal <- comparer.Equals (left[index], right[index])
+ index <- index + 1
+
+ equal
+
+ ///
+ override this.Equals (other : objnull) =
+ match other with
+ | :? EquatableArray<'T> as other -> this.Equals other
+ | _ -> false
+
+ /// Combines the hash codes of the elements, in order, so equal arrays have equal hash codes.
+ override this.GetHashCode () =
+ let mutable hash = HashCode ()
+
+ for item in this.Items do
+ hash.Add item
+
+ hash.ToHashCode ()
+
+ /// Shows the elements in square brackets, separated by semicolons, the way F# shows an array.
+ override this.ToString () =
+ let items = this.Items |> Seq.map (fun item -> $"%A{item}")
+ $"""[| {String.Join ("; ", items)} |]"""
+
+ ///
+ /// Wraps without copying it, so that an
+ /// can be given where an
+ /// is expected.
+ ///
+ /// The elements.
+ static member op_Implicit (items : ImmutableArray<'T>) : EquatableArray<'T> = EquatableArray items
+
+ ///
+ /// The elements of as an ,
+ /// without copying them and never as a default array, so that an
+ /// can be given where an
+ /// is expected.
+ ///
+ /// The array.
+ static member op_Implicit (array : EquatableArray<'T>) : ImmutableArray<'T> = array.Items
+
+ interface IEquatable> with
+ ///
+ member this.Equals other = this.Equals other
+
+ interface IReadOnlyList<'T> with
+ ///
+ member this.Count = this.Length
+
+ ///
+ member this.Item
+ with get index = this.Items[index]
+
+ ///
+ member this.GetEnumerator () : IEnumerator<'T> = (this.Items :> IEnumerable<'T>).GetEnumerator()
+
+ ///
+ member this.GetEnumerator () : IEnumerator = (this.Items :> IEnumerable).GetEnumerator()
+
+///
+/// Functions that create and transform values.
+///
+[]
+[]
+module EquatableArray =
+
+ /// The array without elements.
+ []
+ let empty<'T> : EquatableArray<'T> = EquatableArray ImmutableArray<'T>.Empty
+
+ ///
+ /// Wraps without copying it.
+ ///
+ /// The elements.
+ let ofImmutableArray (items : ImmutableArray<'T>) = EquatableArray items
+
+ ///
+ /// Copies into a new array.
+ ///
+ /// The elements.
+ let ofArray (items : 'T array) = EquatableArray (ImmutableArray.Create<'T> items)
+
+ ///
+ /// Wraps without copying it, as
+ /// does.
+ ///
+ /// The caller gives the array away: the result is immutable only as long as nothing writes to
+ /// any more. A later write changes the elements, the equality and the hash code of the
+ /// result, also where it already is a key of a dictionary or a member of a set.
+ ///
+ ///
+ /// The elements, in an array that is never written to again.
+ let unsafeOfArray (items : 'T array) = EquatableArray (ImmutableCollectionsMarshal.AsImmutableArray<'T> items)
+
+ ///
+ /// Copies into a new array.
+ ///
+ /// The elements.
+ let ofSeq (items : 'T seq) = EquatableArray (ImmutableArray.CreateRange<'T> items)
+
+ ///
+ /// An array that holds only.
+ ///
+ /// The element.
+ let singleton (item : 'T) = EquatableArray (ImmutableArray.Create<'T> item)
+
+ ///
+ /// The elements of as an ,
+ /// without copying them.
+ ///
+ /// The array.
+ let toImmutableArray (array : EquatableArray<'T>) = array.Items
+
+ ///
+ /// Applies to every element of , in order.
+ ///
+ /// The function to apply.
+ /// The array.
+ let map (mapping : 'T -> 'U) (array : EquatableArray<'T>) =
+ let items = array.Items
+ let builder = ImmutableArray.CreateBuilder<'U> items.Length
+
+ for item in items do
+ builder.Add (mapping item)
+
+ EquatableArray (builder.MoveToImmutable ())
diff --git a/src/Cosmos.Sql/FSharp.Azure.Cosmos.Sql.fsproj b/src/Cosmos.Sql/FSharp.Azure.Cosmos.Sql.fsproj
new file mode 100644
index 00000000..70e6c38b
--- /dev/null
+++ b/src/Cosmos.Sql/FSharp.Azure.Cosmos.Sql.fsproj
@@ -0,0 +1,30 @@
+
+
+
+ net10.0
+ $(AssemblyBaseName).Sql
+ true
+ true
+
+ false
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/Cosmos.Sql/ImmutableArrayPatterns.fs b/src/Cosmos.Sql/ImmutableArrayPatterns.fs
new file mode 100644
index 00000000..629bed70
--- /dev/null
+++ b/src/Cosmos.Sql/ImmutableArrayPatterns.fs
@@ -0,0 +1,74 @@
+///
+/// Struct partial active patterns that match an by its
+/// length, so code over the children of a syntax node reads like a list pattern without converting to an F# list.
+///
+/// Each pattern returns a value option made a struct by on its
+/// return value, or a , and its payload is a struct tuple, so a match allocates nothing.
+/// A multi-case active pattern over the lengths would return a heap
+/// on every match, which is why every length is its own partial
+/// pattern and lengths a pattern does not cover fall through to the next case.
+///
+///
+///
+///
+/// match arguments with
+/// | Arr0 -> "no argument"
+/// | Arr1 single -> "one argument"
+/// | Arr2 (first, second) -> "two arguments"
+/// | Arr3 (first, second, third) -> "three arguments"
+/// | ArrN 4 -> "four arguments"
+/// | _ -> "more arguments"
+///
+///
+[]
+module FSharp.Azure.Cosmos.Sql.ImmutableArrayPatterns
+
+open System.Collections.Immutable
+
+///
+/// Matches an array without elements. A default (uninitialized) array counts as empty.
+///
+/// The array to match.
+let (|Arr0|_|) (items : ImmutableArray<'T>) = items.IsDefaultOrEmpty
+
+///
+/// Matches an array of exactly one element and returns that element.
+///
+/// The array to match.
+[]
+let (|Arr1|_|) (items : ImmutableArray<'T>) =
+ if not items.IsDefault && items.Length = 1 then
+ ValueSome items[0]
+ else
+ ValueNone
+
+///
+/// Matches an array of exactly two elements and returns them as a struct tuple, which the pattern binds with or
+/// without the keyword.
+///
+/// The array to match.
+[]
+let (|Arr2|_|) (items : ImmutableArray<'T>) =
+ if not items.IsDefault && items.Length = 2 then
+ ValueSome (struct (items[0], items[1]))
+ else
+ ValueNone
+
+///
+/// Matches an array of exactly three elements and returns them as a struct tuple.
+///
+/// The array to match.
+[]
+let (|Arr3|_|) (items : ImmutableArray<'T>) =
+ if not items.IsDefault && items.Length = 3 then
+ ValueSome (struct (items[0], items[1], items[2]))
+ else
+ ValueNone
+
+///
+/// Matches an array of exactly elements, for the lengths that have no pattern of their own;
+/// index the array to read its elements.
+///
+/// The number of elements to match, written as the argument of the pattern.
+/// The array to match.
+let (|ArrN|_|) (length : int) (items : ImmutableArray<'T>) = (if items.IsDefault then 0 else items.Length) = length
diff --git a/tests/Cosmos.Sql.Tests/EquatableArrayModuleTests.fs b/tests/Cosmos.Sql.Tests/EquatableArrayModuleTests.fs
new file mode 100644
index 00000000..a77daa40
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/EquatableArrayModuleTests.fs
@@ -0,0 +1,57 @@
+namespace FSharp.Azure.Cosmos.Sql.Tests
+
+open System.Collections.Immutable
+open System.Runtime.InteropServices
+open Microsoft.VisualStudio.TestTools.UnitTesting
+
+open FSharp.Azure.Cosmos.Sql
+
+///
+/// The functions of the module , which create and
+/// transform values.
+///
+[]
+type EquatableArrayModuleTests () =
+
+ []
+ member _.``ofArray and ofSeq copy the elements in order`` () =
+ let source = [| 1; 2; 3 |]
+ let fromArray = EquatableArray.ofArray source
+ let fromSeq = EquatableArray.ofSeq (Seq.ofArray source)
+
+ source[0] <- 9
+
+ CollectionAssert.AreEqual ([| 1; 2; 3 |], Seq.toArray fromArray, "ofArray should copy the array it is given.")
+ CollectionAssert.AreEqual ([| 1; 2; 3 |], Seq.toArray fromSeq, "ofSeq should copy the sequence it is given.")
+
+ []
+ member _.``unsafeOfArray wraps the array it is given without copying it`` () =
+ let source = [| 1; 2; 3 |]
+ let array = EquatableArray.unsafeOfArray source
+
+ Assert.AreSame(
+ source,
+ ImmutableCollectionsMarshal.AsArray array.Items,
+ "unsafeOfArray should hold the very array it was given."
+ )
+
+ Assert.AreEqual (EquatableArray.unsafeOfArray [| 1; 2; 3 |], array, "The wrapped array should hold the elements.")
+
+ []
+ member _.``Map, singleton, empty and the conversion back keep the elements`` () =
+ let doubled =
+ EquatableArray.unsafeOfArray [| 1; 2; 3 |]
+ |> EquatableArray.map (fun item -> item * 2)
+
+ Assert.AreEqual (EquatableArray.unsafeOfArray [| 2; 4; 6 |], doubled, "Map should apply the function in order.")
+ Assert.AreEqual (EquatableArray.unsafeOfArray [| 7 |], EquatableArray.singleton 7, "Singleton should hold one element.")
+ Assert.IsTrue (EquatableArray.empty.IsEmpty, "The empty array should have no elements.")
+
+ let immutable = ImmutableArray.Create (4, 5)
+
+ let unwrapped =
+ EquatableArray.ofImmutableArray immutable
+ |> EquatableArray.toImmutableArray
+
+ // ImmutableArray equality compares the reference of the underlying array, so this asserts that nothing was copied
+ Assert.AreEqual>(immutable, unwrapped, "Wrapping and unwrapping should not copy the array.")
diff --git a/tests/Cosmos.Sql.Tests/EquatableArrayTests.fs b/tests/Cosmos.Sql.Tests/EquatableArrayTests.fs
new file mode 100644
index 00000000..6428055b
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/EquatableArrayTests.fs
@@ -0,0 +1,161 @@
+namespace FSharp.Azure.Cosmos.Sql.Tests
+
+open System.Collections.Generic
+open System.Collections.Immutable
+open Microsoft.VisualStudio.TestTools.UnitTesting
+
+open FSharp.Azure.Cosmos.Sql
+
+/// An element that writes its number into the list it is given whenever it is asked for its hash code.
+[]
+type private HashProbe (number : int, asked : List) =
+
+ /// Records the request and gives the number as the hash code.
+ override _.GetHashCode () =
+ asked.Add number
+ number
+
+ /// Equal to itself only: the test needs no equal probes.
+ override this.Equals (other : objnull) = obj.ReferenceEquals (this, other)
+
+///
+/// The struct : its equality and hash code, its default
+/// value, its views and its implicit conversions.
+///
+[]
+type EquatableArrayTests () =
+
+ []
+ member _.``Arrays with equal elements in the same order are equal and hash alike`` () =
+ let first = EquatableArray.unsafeOfArray [| 1; 2; 3 |]
+ let second = EquatableArray.unsafeOfArray [| 1; 2; 3 |]
+
+ Assert.IsTrue (first.Equals second, "Arrays built separately from equal elements should be equal.")
+ Assert.AreEqual (first, second, "Object equality should agree with the typed equality.")
+ Assert.AreEqual (first.GetHashCode (), second.GetHashCode (), "Equal arrays should have equal hash codes.")
+
+ []
+ member _.``Arrays that differ in order, length or an element are not equal`` () =
+ let array = EquatableArray.unsafeOfArray [| 1; 2; 3 |]
+
+ Assert.AreNotEqual (EquatableArray.unsafeOfArray [| 3; 2; 1 |], array, "The order of the elements should matter.")
+ Assert.AreNotEqual (EquatableArray.unsafeOfArray [| 1; 2 |], array, "The length should matter.")
+ Assert.AreNotEqual (EquatableArray.unsafeOfArray [| 1; 2; 4 |], array, "Every element should matter.")
+ Assert.IsFalse (array.Equals (box [| 1; 2; 3 |]), "An array of another type should not be equal.")
+
+ []
+ member _.``The hash code takes in every element once, first to last`` () =
+ let asked = List()
+ // Twenty elements, because the structural hash code of an ImmutableArray takes in only the last eight
+ let array = EquatableArray.unsafeOfArray (Array.init 20 (fun number -> HashProbe (number, asked)))
+
+ array.GetHashCode () |> ignore
+
+ CollectionAssert.AreEqual (
+ Array.init 20 id,
+ asked.ToArray (),
+ "Every element should be asked for its hash code once, in the order of the array."
+ )
+
+ []
+ member _.``The default value behaves as and equals an empty array`` () =
+ let defaultArray = Unchecked.defaultof>
+
+ Assert.IsEmpty (defaultArray, "The default value should have no elements.")
+ Assert.IsTrue (defaultArray.IsEmpty, "The default value should be empty.")
+ Assert.IsTrue (defaultArray.Items.IsEmpty, "The default value should expose an empty, not a default, array.")
+ Assert.AreEqual (EquatableArray.empty, defaultArray, "The default value should equal the empty array.")
+ Assert.AreEqual (
+ EquatableArray.empty.GetHashCode(),
+ defaultArray.GetHashCode (),
+ "The default value should hash like the empty array."
+ )
+
+ []
+ member _.``Elements are compared with their own equality`` () =
+ let first = EquatableArray.unsafeOfArray [| "a"; "b" |]
+ let second = EquatableArray.unsafeOfArray [| "a"; System.String ([| 'b' |]) |]
+
+ Assert.AreEqual (first, second, "Equal strings in different instances should make the arrays equal.")
+
+ []
+ member _.``Equality holds under every comparer, unlike the own equality of ImmutableArray`` () =
+ let first = ImmutableArray.Create (1, 2)
+ let second = ImmutableArray.Create (1, 2)
+
+ // The reason the syntax tree does not hold ImmutableArray: its own equality compares array references
+ Assert.IsFalse (
+ EqualityComparer>.Default.Equals(first, second),
+ "ImmutableArray's default equality should compare references."
+ )
+
+ Assert.IsFalse (
+ (struct (1, first)).Equals(struct (1, second)),
+ "A struct tuple of ImmutableArrays should compare references."
+ )
+
+ Assert.IsTrue (
+ EqualityComparer>.Default.Equals(EquatableArray first, EquatableArray second),
+ "EquatableArray's default equality should compare elements."
+ )
+
+ Assert.IsTrue (
+ (struct (1, EquatableArray first)).Equals(struct (1, EquatableArray second)),
+ "A struct tuple of EquatableArrays should compare elements."
+ )
+
+ let set = HashSet>()
+ set.Add (EquatableArray first) |> ignore
+
+ // Assert.Contains asks the set itself, so the lookup goes through the hash code and the equality of the array
+ Assert.Contains (EquatableArray second, set, "A hash set with the default comparer should find an equal array.")
+
+ []
+ member _.``The read-only list view and the enumerator yield the elements in order`` () =
+ let array = EquatableArray.unsafeOfArray [| 10; 20; 30 |]
+ let list = array :> IReadOnlyList
+
+ Assert.HasCount (3, list, "The list view should report the length.")
+ Assert.AreEqual (20, list[1], "The list view should index the elements.")
+ CollectionAssert.AreEqual ([| 10; 20; 30 |], Seq.toArray list, "The list view should enumerate in order.")
+
+ let mutable sum = 0
+
+ for item in array do
+ sum <- sum + item
+
+ Assert.AreEqual (60, sum, "A for loop over the array should visit every element.")
+
+ // F# applies an implicit conversion without warning FS3391 at method arguments only, so the conversions are
+ // exercised through methods that take the other type
+ static member private AsEquatable (array : EquatableArray) = array
+ static member private AsImmutable (items : ImmutableArray) = items
+
+ []
+ member _.``An ImmutableArray is accepted where an EquatableArray is expected, and the other way round`` () =
+ let immutable = ImmutableArray.Create (4, 5)
+ let equatable = EquatableArrayTests.AsEquatable immutable
+
+ Assert.AreEqual (EquatableArray.unsafeOfArray [| 4; 5 |], equatable, "The conversion should keep the elements.")
+
+ // ImmutableArray equality compares the reference of the underlying array, so these assert that nothing was copied
+ Assert.AreEqual>(immutable, equatable.Items, "The conversion should wrap, not copy, the array.")
+
+ Assert.AreEqual>(
+ immutable,
+ EquatableArrayTests.AsImmutable equatable,
+ "The conversion back should hand out the wrapped array."
+ )
+
+ []
+ member _.``The implicit conversions turn a default array into an empty one`` () =
+ let fromDefault = EquatableArrayTests.AsEquatable Unchecked.defaultof>
+ let toImmutable = EquatableArrayTests.AsImmutable Unchecked.defaultof>
+
+ Assert.AreEqual (EquatableArray.empty, fromDefault, "A default ImmutableArray should convert to the empty array.")
+ Assert.IsFalse (toImmutable.IsDefault, "The default value should not convert to a default ImmutableArray.")
+ Assert.IsEmpty (toImmutable, "The default value should convert to an empty ImmutableArray.")
+
+ []
+ member _.``ToString shows the elements like an F# array`` () =
+ Assert.AreEqual ("[| 1; 2 |]", (EquatableArray.unsafeOfArray [| 1; 2 |]).ToString(), "ToString should list the elements.")
diff --git a/tests/Cosmos.Sql.Tests/FSharp.Azure.Cosmos.Sql.Tests.fsproj b/tests/Cosmos.Sql.Tests/FSharp.Azure.Cosmos.Sql.Tests.fsproj
new file mode 100644
index 00000000..e91fa921
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/FSharp.Azure.Cosmos.Sql.Tests.fsproj
@@ -0,0 +1,36 @@
+
+
+
+
+ net10.0
+ $(AssemblyBaseName).Sql.Tests
+
+ Exe
+ true
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Cosmos.Sql.Tests/ImmutableArrayPatternsTests.fs b/tests/Cosmos.Sql.Tests/ImmutableArrayPatternsTests.fs
new file mode 100644
index 00000000..e15455db
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/ImmutableArrayPatternsTests.fs
@@ -0,0 +1,38 @@
+namespace FSharp.Azure.Cosmos.Sql.Tests
+
+open System.Collections.Immutable
+open Microsoft.VisualStudio.TestTools.UnitTesting
+
+open FSharp.Azure.Cosmos.Sql
+
+///
+/// The active patterns that match an by its length.
+///
+[]
+type ImmutableArrayPatternsTests () =
+
+ []
+ member _.``The length patterns match exactly their length`` () =
+ let describe (items : ImmutableArray) =
+ match items with
+ | Arr0 -> "none"
+ | Arr1 single -> $"one %d{single}"
+ | Arr2 (first, second) -> $"two %d{first} %d{second}"
+ | Arr3 (first, second, third) -> $"three %d{first} %d{second} %d{third}"
+ | ArrN 4 -> "four"
+ | _ -> "more"
+
+ Assert.AreEqual ("none", describe ImmutableArray.Empty, "Arr0 should match an empty array.")
+ Assert.AreEqual ("none", describe Unchecked.defaultof>, "Arr0 should match a default array.")
+ Assert.AreEqual ("one 1", describe (ImmutableArray.Create 1), "Arr1 should match one element.")
+ Assert.AreEqual ("two 1 2", describe (ImmutableArray.Create (1, 2)), "Arr2 should match two elements.")
+ Assert.AreEqual ("three 1 2 3", describe (ImmutableArray.Create (1, 2, 3)), "Arr3 should match three elements.")
+ Assert.AreEqual ("four", describe (ImmutableArray.Create (1, 2, 3, 4)), "ArrN 4 should match four elements.")
+ Assert.AreEqual ("more", describe (ImmutableArray.Create (1, 2, 3, 4, 5)), "Longer arrays should fall through.")
+
+ []
+ member _.``Arr2 binds a struct tuple pattern too`` () =
+ match ImmutableArray.Create ("a", "b") with
+ | Arr2 (struct (first, second)) ->
+ Assert.AreEqual ("ab", first + second, "The struct tuple pattern should bind both elements.")
+ | _ -> Assert.Fail "Arr2 should match an array of two elements."
diff --git a/tests/Cosmos.Sql.Tests/TestCategories.fs b/tests/Cosmos.Sql.Tests/TestCategories.fs
new file mode 100644
index 00000000..ecb87209
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/TestCategories.fs
@@ -0,0 +1,18 @@
+// The categories of the FSharp.Azure.Cosmos.Sql tests. Every test here is a fast, emulator-free unit test, so every
+// category also carries "Unit", which --filter TestCategory=Unit selects. The attributes are sorted by name.
+namespace FSharp.Azure.Cosmos.Sql.Tests
+
+open System.Collections.Generic
+open Microsoft.VisualStudio.TestTools.UnitTesting
+
+///
+/// Categorizes a unit test of the collection types that the syntax tree is built from:
+/// ,
+/// and
+/// .
+///
+type CollectionsUnitTestCategoryAttribute () =
+ inherit TestCategoryBaseAttribute ()
+
+ ///
+ override _.TestCategories = [| "Unit"; "Collections" |] :> IList
diff --git a/tests/Cosmos.Sql.Tests/testconfig.json b/tests/Cosmos.Sql.Tests/testconfig.json
new file mode 100644
index 00000000..b43d5de5
--- /dev/null
+++ b/tests/Cosmos.Sql.Tests/testconfig.json
@@ -0,0 +1,9 @@
+{
+ "mstest": {
+ "parallelism": {
+ "enabled": true,
+ "scope": "method",
+ "workers": 0
+ }
+ }
+}
\ No newline at end of file