Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions samples/FSharp.CosmosDb.Samples/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ let getFamilies conn =
|> Cosmos.parameters [ "@lastName", box "Powell" ]
|> Cosmos.execAsync

let getParents conn =
conn
|> Cosmos.linq<Family, Parent[]> (fun families ->
cosmosQuery {
for family in families do
where (family.LastName = "Powell")
select family.Parents
})
|> Cosmos.execAsync

let updateFamily conn id pk =
conn
|> Cosmos.update<Family>
Expand Down Expand Up @@ -112,6 +122,12 @@ let main argv =
families
|> AsyncSeq.iter (fun f -> printfn "Got: %A" f)

let firstNames = getParents conn

do!
firstNames
|> AsyncSeq.iter (printfn "Got: %A")

let updatePowell = updateFamily conn "Powell.1" "Powell"

do!
Expand Down
11 changes: 11 additions & 0 deletions src/FSharp.CosmosDb/Cosmos.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace FSharp.CosmosDb

open Microsoft.Azure.Cosmos
open System
open System.Linq

[<RequireQualifiedAccess>]
module Cosmos =
Expand Down Expand Up @@ -61,6 +62,15 @@ module Cosmos =
Parameters = q.Parameters @ arr }
| _ -> failwith "Only the Query discriminated union supports parameters"

// --- LINQ --- //

let linq<'TSource, 'TResult> (query: IQueryable<'TSource> -> IQueryable<'TResult>) op : ContainerOperation<'TResult> =
Linq
{ Connection = op;
Query = fun container ->
container.GetItemLinqQueryable<'TSource>()
|> query }

// --- INSERT --- //

let insertMany<'T> (values: 'T list) op =
Expand Down Expand Up @@ -116,6 +126,7 @@ module Cosmos =
let execAsync<'T> (op: ContainerOperation<'T>) =
match op with
| Query op -> OperationHandling.execQuery getClient op
| Linq op -> OperationHandling.execLinq getClient op
| Insert op -> OperationHandling.execInsert getClient op
| Update op -> OperationHandling.execUpdate getClient op
| Delete op -> OperationHandling.execDelete getClient op
Expand Down
1 change: 1 addition & 0 deletions src/FSharp.CosmosDb/FSharp.CosmosDb.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Compile Include="PartitionKeyAttribute.fs" />
<Compile Include="Types.fs" />
<Compile Include="OperationHandling.fs" />
<Compile Include="QueryBuilder.fs" />
<Compile Include="Cosmos.fs" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
Expand Down
22 changes: 22 additions & 0 deletions src/FSharp.CosmosDb/OperationHandling.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module internal OperationHandling

open FSharp.CosmosDb
open Microsoft.Azure.Cosmos
open Microsoft.Azure.Cosmos.Linq
open FSharp.Control
open System

Expand Down Expand Up @@ -67,6 +68,27 @@ let execQueryBatch (getClient: ConnectionOperation -> CosmosClient) (op: QueryOp
| None ->
failwith "Unable to construct a query as some values are missing across the database, container name and query"

let execLinq (getClient: ConnectionOperation -> CosmosClient) (op: LinqOp<'T>) =
let connInfo = op.Connection
let client = getClient connInfo

let result =
maybe {
let! databaseId = connInfo.DatabaseId
let! containerName = connInfo.ContainerName

let db = client.GetDatabase databaseId

let container = db.GetContainer containerName

return op.Query(container).ToFeedIterator<'T>()
}

match result with
| Some result -> ofAsyncFeedIterator result
| None ->
failwith "Unable to construct a query as some values are missing across the database, container name and query"

let execInsert (getClient: ConnectionOperation -> CosmosClient) (op: InsertOp<'T>) =
let connInfo = op.Connection
let client = getClient connInfo
Expand Down
233 changes: 233 additions & 0 deletions src/FSharp.CosmosDb/QueryBuilder.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
namespace FSharp.CosmosDb

open System
open System.Linq
open FSharp.Linq
open FSharp.Quotations
open FSharp.Quotations.DerivedPatterns
open FSharp.Quotations.ExprShape

type CosmosQueryBuilder() =
inherit QueryBuilder()

static let rec replace =
// Replace F# functions with BCL equivalents.
function
| SpecificCall <@@ abs @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<int8> then
<@@ (Math.Abs: int8 -> int8) %%e @@>
elif typ = typeof<int16> then
<@@ (Math.Abs: int16 -> int16) %%e @@>
elif typ = typeof<int32> then
<@@ (Math.Abs: int32 -> int32) %%e @@>
elif typ = typeof<int64> then
<@@ (Math.Abs: int64 -> int64) %%e @@>
elif typ = typeof<float32> then
<@@ (Math.Abs: float32 -> float32) %%e @@>
elif typ = typeof<float> then
<@@ (Math.Abs: float -> float) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Abs: decimal -> decimal) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof abs}': {typ.FullName}"

| SpecificCall <@@ acos @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Acos(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Acos %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof acos}': {typ.FullName}"

| SpecificCall <@@ asin @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Asin(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Asin %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof asin}': {typ.FullName}"

| SpecificCall <@@ atan @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Atan(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Atan %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof atan}': {typ.FullName}"

| SpecificCall <@@ ceil @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Ceiling(float %%e) @@>
elif typ = typeof<float> then
<@@ (Math.Ceiling: float -> float) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Ceiling: float -> float) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof ceil}': {typ.FullName}"

| SpecificCall <@@ cos @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Cos(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Cos %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof cos}': {typ.FullName}"

| SpecificCall <@@ exp @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Exp(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Exp %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof exp}': {typ.FullName}"

| SpecificCall <@@ floor @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Floor(float %%e) @@>
elif typ = typeof<float> then
<@@ (Math.Floor: float -> float) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Floor: decimal -> decimal) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof floor}': {typ.FullName}"

| SpecificCall <@@ log @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Log(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Log %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof log}': {typ.FullName}"

| SpecificCall <@@ log10 @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Log10(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Log10 %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof log10}': {typ.FullName}"

| SpecificCall <@@ ( ** ) @@> (_, [ typ; _ ], [ x; n ])
| SpecificCall <@@ pown @@> (_, [ typ; _ ], [ x; n ]) ->
let x = replace x
let n = replace n

if typ = typeof<float> then
<@@ Math.Pow(%%x, float %%n) @@>
else
<@@ Math.Pow(float %%x, float %%n) @@>

| SpecificCall <@@ round @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Round(float %%e) @@>
elif typ = typeof<float> then
<@@ (Math.Round: float -> float) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Round: decimal -> decimal) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof round}': {typ.FullName}"

| SpecificCall <@@ sign @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<int8> then
<@@ (Math.Sign: int8 -> int) %%e @@>
elif typ = typeof<int16> then
<@@ (Math.Sign: int16 -> int) %%e @@>
elif typ = typeof<int> then
<@@ (Math.Sign: int -> int) %%e @@>
elif typ = typeof<int64> then
<@@ (Math.Sign: int64 -> int) %%e @@>
elif typ = typeof<float32> then
<@@ (Math.Sign: float32 -> int) %%e @@>
elif typ = typeof<float> then
<@@ (Math.Sign: float -> int) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Sign: decimal -> int) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof sign}': {typ.FullName}"

| SpecificCall <@@ sin @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Sin(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Sin %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof sin}': {typ.FullName}"

| SpecificCall <@@ sqrt @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float> then
<@@ Math.Sqrt %%e @@>
else
<@@ Math.Sqrt(float %%e) @@>

| SpecificCall <@@ tan @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Tan(float %%e) @@>
elif typ = typeof<float> then
<@@ Math.Tan %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof tan}': {typ.FullName}"

| SpecificCall <@@ truncate @@> (_, [ typ ], [ arg ]) ->
let e = replace arg

if typ = typeof<float32> then
<@@ Math.Truncate(float %%e) @@>
elif typ = typeof<float> then
<@@ (Math.Truncate: float -> float) %%e @@>
elif typ = typeof<decimal> then
<@@ (Math.Truncate: decimal -> decimal) %%e @@>
else
failwith $"Invalid argument type for translation of '{nameof truncate}': {typ.FullName}"

// Pipeline operator removal.
| SpecificCall <@@ (|>) @@> (_, _, [ x; f ]) ->
let f = replace f
let x = replace x
Expr.Application(f, x)

| SpecificCall <@@ (<|) @@> (_, _, [ f; x ]) ->
let f = replace f
let x = replace x
Expr.Application(f, x)

| ShapeVar v -> Expr.Var v
| ShapeLambda(v, expr) -> Expr.Lambda(v, replace expr)
| ShapeCombination(o, args) -> RebuildShapeCombination(o, List.map replace args)

member _.Run(e: Expr<QuerySource<'a, IQueryable>>) =
let r = Expr.Cast<QuerySource<'a, IQueryable>>(replace e)
base.Run r

[<AutoOpen>]
module QueryBuilder =
let cosmosQuery = CosmosQueryBuilder()
6 changes: 6 additions & 0 deletions src/FSharp.CosmosDb/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open System.Threading.Tasks
open System.Collections.Concurrent
open System
open System.Collections.Generic
open System.Linq

module internal Caching =
let private clientCache =
Expand Down Expand Up @@ -86,6 +87,10 @@ type QueryOp<'T> =
Query: string option
Parameters: (string * obj) list }

type LinqOp<'T> =
{ Connection: ConnectionOperation
Query: Container -> IQueryable<'T> }

type InsertOp<'T> =
{ Connection: ConnectionOperation
Values: 'T list }
Expand Down Expand Up @@ -116,6 +121,7 @@ type ReplaceOp<'T> =

type ContainerOperation<'T> =
| Query of QueryOp<'T>
| Linq of LinqOp<'T>
| Insert of InsertOp<'T>
| Update of UpdateOp<'T>
| Delete of DeleteOp<'T>
Expand Down