From fbb727b04384349a2c7980586d1d88dfbd170d91 Mon Sep 17 00:00:00 2001 From: "Angel D. Munoz" Date: Wed, 23 Dec 2020 16:03:23 -0700 Subject: [PATCH] Add Aggregates (#27) * feature add builders for supplied types * feature add aggregate builder * fix constraint warning --- src/Mondocks/Aggregation.fs | 139 +++++++++++++++++++++++++++++++++++- src/Mondocks/Types.fs | 138 +++++++++++++++++++++++++++++++++-- 2 files changed, 271 insertions(+), 6 deletions(-) diff --git a/src/Mondocks/Aggregation.fs b/src/Mondocks/Aggregation.fs index 56fc8d3..c767e6c 100644 --- a/src/Mondocks/Aggregation.fs +++ b/src/Mondocks/Aggregation.fs @@ -1,7 +1,5 @@ namespace Mondocks -open System -open System.Collections.Generic open Mondocks.Types module Aggregation = @@ -195,3 +193,140 @@ module Aggregation = /// The name of the collection to perform this query against /// returns a DistinctCommandBuilder let distinct (collection: string) = DistinctCommandBuilder(collection) + + [] + module Aggregate = + type AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern> = + { aggregate: obj + pipeline: obj seq + explain: Option + allowDiskUse: Option + cursor: Option<{| batchSize: int |}> + maxTimeMS: Option + bypassDocumentValidation: Option + readConcern: Option<'ReadConcern> + collation: Option + 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() + + /// + /// An array of aggregation pipeline stages that process and transform the document stream as part of the aggregation pipeline. + /// + /// + /// 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" |} + /// |} + /// |} + /// ] + /// } + /// + [] + member __.Pipeline(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, pipeline: obj seq) = + { state with pipeline = pipeline } + + /// + /// 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 FindResult + /// use instead BsonDocument as the result of RunCommand or RunCommandAsync + /// since using explain will bring back a higly dynamic document + /// + [] + member __.Explain(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, explain: bool) = + { state with explain = Some explain } + + [] + member __.AllowDiskUse(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, + allowDiskUse: bool) = + { state with + allowDiskUse = Some allowDiskUse } + + [] + member __.Cursor(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, cursorBatchSize: int) = + { state with + cursor = Some {| batchSize = cursorBatchSize |} } + + [] + member __.MaxtimeMs(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, maxTimeMs: int) = + { state with + maxTimeMS = Some maxTimeMs } + + [] + member __.BypassDocumentValidation(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, + bypassDocumentValidation: bool) = + { state with + bypassDocumentValidation = Some bypassDocumentValidation } + + [] + member __.ReadConcern(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, + readConcern: 'ReadConcern) = + { state with + readConcern = Some readConcern } + + [] + member __.Collation(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, + collation: Collation) = + { state with + collation = Some collation } + + [] + member __.Hint(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, hint: 'Hint) = + { state with hint = Some hint } + + [] + member __.Comment(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, comment: 'Comment) = + { state with comment = Some comment } + + [] + member __.WriteConcern(state: AggregateCommand<'ReadConcern, 'Hint, 'Comment, 'WriteConcern>, + writeConcern: 'WriteConcern) = + { state with + writeConcern = Some writeConcern } + + /// Creates an aggregate command that will work against the specified collection + /// The name of the collection to perform this query against + /// returns a AggregateCommandBuilder + let aggregate (collection: string) = AggregateCommandBuilder(collection) + + /// Creates an aggregate command that is agnostic to collections + /// The name of the collection to perform this query against + /// returns a AggregateCommandBuilder + let aggregateAgnostic = AggregateCommandBuilder() diff --git a/src/Mondocks/Types.fs b/src/Mondocks/Types.fs index 1b6fe75..ccd892e 100644 --- a/src/Mondocks/Types.fs +++ b/src/Mondocks/Types.fs @@ -2,7 +2,6 @@ namespace Mondocks.Types open MongoDB.Bson.Serialization.Attributes open Mondocks -open System.Collections.Generic type Collation = { locale: string @@ -14,6 +13,9 @@ type Collation = maxVariable: Option backwards: Option } + interface IBuilder with + member __.ToJSON() = Json.Serialize __ + type DeleteQuery<'Delete, 'Hint, 'Comment> = { /// that represents the matching criteria q: 'Delete @@ -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 @@ -41,9 +46,7 @@ type Cursor<'T> = { firstBatch: seq<'T>; ns: string } type FindResult<'T> = { cursor: Cursor<'T>; ok: float } [] -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 } @@ -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 } + + [] + member __.CaseLevel(state: Collation, caseLevel: bool) = + { state with + caseLevel = Some caseLevel } + + [] + member __.CaseFirst(state: Collation, caseFirst: bool) = + { state with + caseFirst = Some caseFirst } + + [] + member __.Strength(state: Collation, strength: bool) = { state with strength = Some strength } + + [] + member __.NumericOrdering(state: Collation, numericOrdering: bool) = + { state with + numericOrdering = Some numericOrdering } + + [] + member __.Alternate(state: Collation, alternate: bool) = + { state with + alternate = Some alternate } + + [] + member __.MaxVariable(state: Collation, maxVariable: bool) = + { state with + maxVariable = Some maxVariable } + + [] + 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 + + + [] + member __.Query(state: DeleteQuery<'Delete, 'Hint, 'Comment>, query: 'Delete) = { state with q = query } + + [] + member __.Limit(state: DeleteQuery<'Delete, 'Hint, 'Comment>, limit: int) = { state with limit = limit } + + [] + member __.Collation(state: DeleteQuery<'Delete, 'Hint, 'Comment>, collation: Collation) = + { state with + collation = Some collation } + + [] + member __.Hint(state: DeleteQuery<'Delete, 'Hint, 'Comment>, hint: 'Hint) = { state with hint = Some hint } + + [] + 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 + + + [] + member __.Query(state: UpdateQuery<'Query, 'Update, 'Hint>, query: 'Query) = { state with q = query } + + [] + member __.Update(state: UpdateQuery<'Query, 'Update, 'Hint>, update: 'Update) = { state with u = update } + + [] + member __.Upsert(state: UpdateQuery<'Query, 'Update, 'Hint>, upsert: bool) = { state with upsert = Some upsert } + + [] + member __.Multi(state: UpdateQuery<'Query, 'Update, 'Hint>, multi: bool) = { state with multi = Some multi } + + [] + member __.Collation(state: UpdateQuery<'Query, 'Update, 'Hint>, collation: Collation) = + { state with + collation = Some collation } + + + [] + 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) + + [] + 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()