Skip to content

Commit

Permalink
Add Aggregates (#27)
Browse files Browse the repository at this point in the history
* feature add builders for supplied types

* feature add aggregate builder

* fix constraint warning
  • Loading branch information
AngelMunoz committed Dec 23, 2020
1 parent 828cd8c commit fbb727b
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 6 deletions.
139 changes: 137 additions & 2 deletions src/Mondocks/Aggregation.fs
@@ -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
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()

0 comments on commit fbb727b

Please sign in to comment.