Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Aggregates #27

Merged
merged 3 commits into from
Dec 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
139 changes: 137 additions & 2 deletions src/Mondocks/Aggregation.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
namespace Mondocks

open System
open System.Collections.Generic
open Mondocks.Types

module Aggregation =
Expand Down Expand Up @@ -195,3 +193,140 @@ module Aggregation =
/// <param name="collection">The name of the collection to perform this query against</param>
/// <returns>returns a <see cref="Mondocks.Aggregation.Distinct.DistinctCommandBuilder">DistinctCommandBuilder</see></returns>
let distinct (collection: string) = DistinctCommandBuilder(collection)

[<AutoOpen>]
module Aggregate =
type AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern> =
{ aggregate: obj
pipeline: obj seq
explain: Option<bool>
allowDiskUse: Option<bool>
cursor: Option<{| batchSize: int |}>
maxTimeMS: Option<int>
bypassDocumentValidation: Option<bool>
readConcern: Option<'ReadConcern>
collation: Option<Collation>
hint: Option<'Hint>
comment: Option<'Comment>
writeConcern: Option<'WriteConcern> }

interface IBuilder with
member __.ToJSON() = Json.Serialize __

type AggregateCommandBuilder() =
let mutable aggregate = box 1

member __.Aggregate
with get () = aggregate
and set (value) = aggregate <- value

new(name: string) as this =
AggregateCommandBuilder()
then this.Aggregate <- box name

member __.Yield _ =
{ aggregate = __.Aggregate
pipeline = []
explain = None
allowDiskUse = None
cursor = Some {| batchSize = 0 |}
maxTimeMS = None
bypassDocumentValidation = None
readConcern = None
collation = None
hint = None
comment = None
writeConcern = None }

member __.Run(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>) =
(state :> IBuilder).ToJSON()

/// <summary>
/// An array of aggregation pipeline stages that process and transform the document stream as part of the aggregation pipeline.
/// </summary>
/// <example>
/// aggregate "users" {
/// // gets all of the distinc emails from users named "Frank"
/// pipeline [
/// box {| ``$project`` = {| Price = 1; Tag = 1 |} |}
/// box {| ``$group`` =
/// {| _id = "$Tag";
/// items = {| ``$sum`` = 1 |};
/// total = {| ``$sum`` = "$Price" |}
/// |}
/// |}
/// ]
/// }
/// </example>
[<CustomOperation("pipeline")>]
member __.Pipeline(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, pipeline: obj seq) =
{ state with pipeline = pipeline }

/// <summary>
/// Optional. Specifies to return the information on the processing of the pipeline.
/// Not available in multi-document transactions.
/// ***NOTE***: If you use this don't use <see cref="Mondocks.Types.FindResult">FindResult</see>
/// use instead <see cref="MongoDB.Bson.BsonDocument">BsonDocument</see> as the result of RunCommand or RunCommandAsync
/// since using explain will bring back a higly dynamic document
/// </summary>
[<CustomOperation("explain")>]
member __.Explain(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, explain: bool) =
{ state with explain = Some explain }

[<CustomOperation("allow_disk_use")>]
member __.AllowDiskUse(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>,
allowDiskUse: bool) =
{ state with
allowDiskUse = Some allowDiskUse }

[<CustomOperation("cursor_batch_size")>]
member __.Cursor(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, cursorBatchSize: int) =
{ state with
cursor = Some {| batchSize = cursorBatchSize |} }

[<CustomOperation("max_time_ms")>]
member __.MaxtimeMs(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, maxTimeMs: int) =
{ state with
maxTimeMS = Some maxTimeMs }

[<CustomOperation("bypass_document_validation")>]
member __.BypassDocumentValidation(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>,
bypassDocumentValidation: bool) =
{ state with
bypassDocumentValidation = Some bypassDocumentValidation }

[<CustomOperation("read_concern")>]
member __.ReadConcern(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>,
readConcern: 'ReadConcern) =
{ state with
readConcern = Some readConcern }

[<CustomOperation("collation")>]
member __.Collation(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>,
collation: Collation) =
{ state with
collation = Some collation }

[<CustomOperation("hint")>]
member __.Hint(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, hint: 'Hint) =
{ state with hint = Some hint }

[<CustomOperation("comment")>]
member __.Comment(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, comment: 'Comment) =
{ state with comment = Some comment }

[<CustomOperation("write_concern")>]
member __.WriteConcern(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>,
writeConcern: 'WriteConcern) =
{ state with
writeConcern = Some writeConcern }

/// <summary>Creates an aggregate command that will work against the specified collection</summary>
/// <param name="collection">The name of the collection to perform this query against</param>
/// <returns>returns a <see cref="Mondocks.Aggregation.Aggregate.AggregateCommandBuilder">AggregateCommandBuilder</see></returns>
let aggregate (collection: string) = AggregateCommandBuilder(collection)

/// <summary>Creates an aggregate command that is <a href="https://docs.mongodb.com/manual/reference/command/aggregate/#command-fields">agnostic to collections</a></summary>
/// <param name="collection">The name of the collection to perform this query against</param>
/// <returns>returns a <see cref="Mondocks.Aggregation.Aggregate.AggregateCommandBuilder">AggregateCommandBuilder</see></returns>
let aggregateAgnostic = AggregateCommandBuilder()
138 changes: 134 additions & 4 deletions src/Mondocks/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace Mondocks.Types

open MongoDB.Bson.Serialization.Attributes
open Mondocks
open System.Collections.Generic

type Collation =
{ locale: string
Expand All @@ -14,6 +13,9 @@ type Collation =
maxVariable: Option<bool>
backwards: Option<bool> }

interface IBuilder with
member __.ToJSON() = Json.Serialize __

type DeleteQuery<'Delete, 'Hint, 'Comment> =
{ /// that represents the matching criteria
q: 'Delete
Expand All @@ -23,6 +25,9 @@ type DeleteQuery<'Delete, 'Hint, 'Comment> =
hint: Option<'Hint>
comment: Option<'Comment> }

interface IBuilder with
member __.ToJSON() = Json.Serialize __

type UpdateQuery<'Query, 'Update, 'Hint> =
{ /// the matching criteria
q: 'Query
Expand All @@ -41,9 +46,7 @@ type Cursor<'T> = { firstBatch: seq<'T>; ns: string }
type FindResult<'T> = { cursor: Cursor<'T>; ok: float }

[<BsonIgnoreExtraElements>]
type InsertResult =
{ n: int
ok: float }
type InsertResult = { n: int; ok: float }

type UpdateResult = { n: int; nModified: int; ok: float }
type DeleteResult = { n: int; ok: float }
Expand All @@ -61,3 +64,130 @@ type DropIndexResult = { nIndexesWas: int; ok: float }
type CountResult = { n: int; ok: float }

type DistinctResult<'TValue> = { values: seq<'TValue>; ok: float }

module Builders =

type CollationBuilder(locale: string) =

member __.Yield _ =
{ locale = ""
caseLevel = None
caseFirst = None
strength = None
numericOrdering = None
alternate = None
maxVariable = None
backwards = None }

member __.Run(state: Collation) = { state with locale = locale }

[<CustomOperation("case_level")>]
member __.CaseLevel(state: Collation, caseLevel: bool) =
{ state with
caseLevel = Some caseLevel }

[<CustomOperation("case_first")>]
member __.CaseFirst(state: Collation, caseFirst: bool) =
{ state with
caseFirst = Some caseFirst }

[<CustomOperation("strength")>]
member __.Strength(state: Collation, strength: bool) = { state with strength = Some strength }

[<CustomOperation("numeric_ordering")>]
member __.NumericOrdering(state: Collation, numericOrdering: bool) =
{ state with
numericOrdering = Some numericOrdering }

[<CustomOperation("alternate")>]
member __.Alternate(state: Collation, alternate: bool) =
{ state with
alternate = Some alternate }

[<CustomOperation("max_variable")>]
member __.MaxVariable(state: Collation, maxVariable: bool) =
{ state with
maxVariable = Some maxVariable }

[<CustomOperation("backwards")>]
member __.Backwards(state: Collation, backwards: bool) =
{ state with
backwards = Some backwards }



type DeleteQueryBuilder() =
member __.Yield _ =
{ q = {| |}
limit = 0
collation = None
hint = None
comment = None }

member __.Run(state: DeleteQuery<'Delete, 'Hint, 'Comment>) = state


[<CustomOperation("query")>]
member __.Query(state: DeleteQuery<'Delete, 'Hint, 'Comment>, query: 'Delete) = { state with q = query }

[<CustomOperation("limit")>]
member __.Limit(state: DeleteQuery<'Delete, 'Hint, 'Comment>, limit: int) = { state with limit = limit }

[<CustomOperation("collation")>]
member __.Collation(state: DeleteQuery<'Delete, 'Hint, 'Comment>, collation: Collation) =
{ state with
collation = Some collation }

[<CustomOperation("hint")>]
member __.Hint(state: DeleteQuery<'Delete, 'Hint, 'Comment>, hint: 'Hint) = { state with hint = Some hint }

[<CustomOperation("comment")>]
member __.Comment(state: DeleteQuery<'Delete, 'Hint, 'Comment>, comment: 'Comment) =
{ state with comment = Some comment }

type UpdateQueryBuilder() =
member __.Yield _ =
{ q = {| |}
u = {| |}
upsert = None
multi = None
collation = None
arrayFilters = None
hint = None }

member __.Run(state: UpdateQuery<'Query, 'Update, 'Hint>) = state


[<CustomOperation("query")>]
member __.Query(state: UpdateQuery<'Query, 'Update, 'Hint>, query: 'Query) = { state with q = query }

[<CustomOperation("update")>]
member __.Update(state: UpdateQuery<'Query, 'Update, 'Hint>, update: 'Update) = { state with u = update }

[<CustomOperation("upsert")>]
member __.Upsert(state: UpdateQuery<'Query, 'Update, 'Hint>, upsert: bool) = { state with upsert = Some upsert }

[<CustomOperation("multi")>]
member __.Multi(state: UpdateQuery<'Query, 'Update, 'Hint>, multi: bool) = { state with multi = Some multi }

[<CustomOperation("collation")>]
member __.Collation(state: UpdateQuery<'Query, 'Update, 'Hint>, collation: Collation) =
{ state with
collation = Some collation }


[<CustomOperation("array_filters")>]
member __.ArrayFilters(state: UpdateQuery<'Query, 'Update, 'Hint>, arrayFilters: obj seq) =
{ state with
arrayFilters = Some arrayFilters }

member this.ArrayFilters(state: UpdateQuery<'Query, 'Update, 'Hint>, arrayFilters: 'T seq) =
this.ArrayFilters(state, arrayFilters |> Seq.map box)

[<CustomOperation("hint")>]
member __.Hint(state: UpdateQuery<'Query, 'Update, 'Hint>, hint: 'Hint) = { state with hint = hint }


let collation (locale: string) = CollationBuilder(locale)
let deletequery = DeleteQueryBuilder()
let updatequery = UpdateQueryBuilder()