Skip to content

Instantly share code, notes, and snippets.

@visemet
Created July 26, 2013 22:32
Show Gist options
  • Save visemet/6092700 to your computer and use it in GitHub Desktop.
Save visemet/6092700 to your computer and use it in GitHub Desktop.
Defines the structure for the various query and update operators that are supported by MongoDB in a way that is natural to use from F#. Note that the (?) operator is only necessary for dealing with an unknown schema, rather than a user-defined class in which case (.) would provide type safety. The code quotation is traversed to construct the cor…

Intend to support all selectors and operators listed here in one form or another.

let (?) (doc : BsonDocument) (field : string) =
    unbox doc.[field]

let (?<-) (doc : BsonDocument) (field : string) value =
    doc.[field] = unbox value |> ignore

Query Selectors

Comparison

$all
module Query =
    let all (x : 'a list) (y : 'a list) : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?tags |> Query.all [ "appliances"; "school"; "book" ] @>
$eq

There is no actual $eq operator, but you get the idea...

bson <@ fun (x : BsonDocument) -> x?qty = 20 @>
$gt
bson <@ fun (x : BsonDocument) -> x?qty > 20 @>
$gte
bson <@ fun (x : BsonDocument) -> x?qty >= 20 @>
$in

Decided to add single-quote after identifier to avoid name conflict.

module Query =
    let in' (x : 'a list) (y : 'a) : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?qty |> Query.in' [ 5; 15 ] @>
$lt
bson <@ fun (x : BsonDocument) -> x?qty < 20 @>
$lte
bson <@ fun (x : BsonDocument) -> x?qty <= 20 @>
$ne
bson <@ fun (x : BsonDocument) -> x?qty <> 20 @>
$nin
module Query =
    let nin (x : 'a list) (y : 'a) : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?qty |> Query.nin [ 5; 15 ] @>

Logical

$or
bson <@ fun x -> x?price = 1.99 && (x?qty < 20 || x?sale = true) @>
$and
bson <@ fun x -> x?price = 1.99 && x?qty < 20 && x?sale = true @>
$not
bson <@ fun x -> not (x?price > 1.99) @>
$nor
module Query =
    let nor (x : bool list) : bool = invalidOp "not implemented"

bson <@ fun x -> Query.nor [ x?price = 1.99; x?qty < 20; x?sale = true ] @>

Element

$exists

Could define a pair of functions n/exists, or use a similar semantic of { $exists: <bool> }.

module Query =
    let exists x : bool = invalidOp "not implemented"
    let nexists x : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?qty |> Query.exists && x?qty |> Query.nin [ 5; 15 ] @>
bson <@ fun (x : BsonDocument) -> x?qty |> Query.nexists || x?qty |> Query.in [ 5; 15 ] @>
$mod
bson <@ fun (x : BsonDocument) -> x?qty % 4 = 0 @>
$type

All BsonValue instances have a BsonType property, which could be used to perform the check with regard to an enum (that has meaningful names). Doing so requires an explicit upcast, however.

bson <@ fun (x : BsonDocument) -> (x?price :> BsonValue).BsonType = BsonType.Double @>

The alternative is to define a function that would take an integer or type.

module Query =
    let type' (x : BsonType) y : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?price |> Query.type' BsonType.Double @>

JavaScript

$where
module Query =
    let where (x : string) y : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x |> Query.where "this.credits == this.debits" @>
$regex
let (=~) input pattern =
    System.Text.RegularExpressions.Regex.IsMatch(input, pattern)

bson <@ fun (x : BsonDocument) -> x?field =~ "/acme.*corp/i" @>

Array

$elemMatch
module Query =
    let elemMatch x y : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) ->
    x?array |> Query.elemMatch (bson <@ fun (y : BsonDocument) -> y?value1 = 1 && y?value2 > 1 @>) @>
$size
module Query =
    let size (x : int) y : bool = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> x?field |> Query.size 2 @>

Update Operators

Since the update command is able to affect multiple fields with different operations, the return type of the function should be a unit list rather than a bool.

Fields

$inc
bson <@ fun (x : BsonDocument) -> [ x?age <- (+) 1 ] @>
$rename

TODO

$setOnInsert

TODO

$set

Should we also use an option type here, i.e. Some x, to parallel the $unset operator?

bson <@ fun (x : BsonDocument) -> [ x?age <- 1 ] @>
$unset
bson <@ fun (x : BsonDocument) -> [ x?age <- None ] @>

Array

Operators
$
bson <@ fun (x : BsonDocument) -> [ x ? grades ? ``$`` <- 82 ] @>
$addToSet
module Update =
    let addToSet (x : 'a) (y : 'a list) : 'a list = invalidOp "not implemented"

bson <@ (x : BsonDocument) -> [ x?scores <- Update.addToSet 89 ] @>
$pop

Could define as two separate operators: popleft and popright, or use a similar semantic of { $pop: { field: +/- 1 } }.

module Update =
    let popleft (x : 'a list) : 'a list = invalidOp "not implemented"
    let popright (x : 'a list) : 'a list = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> [ x?field <- Update.popleft ] @>
bson <@ fun (x : BsonDocument) -> [ x?field <- Update.popright ] @>
$pullAll
module Update =
    let pullAll (x : 'a list) (y : 'a list) : 'a list = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> [ x?field1 <- Update.pullAll [ value1; value2; value3 ] ] @>
$push
module Update =
    let push (x : 'a) (y : 'a list) : 'a list = invalidOp "not implemented"

bson <@ (x : BsonDocument) -> [ x?scores <- Update.push 89 ] @>
Modifiers
$each
module Update =
    let each (x : 'a -> 'a list -> 'a list) (y : 'a list) (z : 'a list) : 'a list = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> [ x?field <- Update.each Update.addToSet [ value1; value2; ... ] ] @>
bson <@ fun (x : BsonDocument) -> [ x?field <- Update.each Update.push [ value1; value2; ... ] ] @>
$slice
module Update =
    let slice (x : int) (y : 'a list) : 'a list = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) -> [ x?grades <- Update.each Update.push [ 80; 78; 86 ] >> Update.slide -5 ] @>
$sort
module Update =
    let sort (x : 'a) (y : 'b list) : 'b list = invalidOp "not implemented"

bson <@ fun (x : BsonDocument) ->
    [ x?quizzes <- Update.each Update.push [ { Id = 3; Score = 8 }
                                             { Id = 4; Score = 7 }
                                             { Id = 5; Score = 6 } ]
                >> Update.sort (bson <@ fun (y : BsonDocument) -> y?score = 1 @>)
                >> Update.slice -5 ] @>

Bitwise

$bit
bson <@ (x : BsonDocument) -> [ x?field <- (&&&) 5 ] @>
bson <@ (x : BsonDocument) -> [ x?field <- (|||) 5 ] @>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment