diff --git a/README.md b/README.md index 16de633..dda02ac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ [![Binder](https://notebooks.gesis.org/binder/badge_logo.svg)](https://notebooks.gesis.org/binder/v2/gh/AngelMunoz/Mondocks/HEAD) > ``` -> dotnet add package Mondocks +> dotnet add package Mondocks.Net +> # or for fable/nodejs +> dotnet add package Mondocks.Fable > ``` > This library is based on the mongodb extended json spec and mongodb manual reference diff --git a/notebooks/CRUD-Guide.ipynb b/notebooks/CRUD-Guide.ipynb index f856d53..2c5799c 100644 --- a/notebooks/CRUD-Guide.ipynb +++ b/notebooks/CRUD-Guide.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, "source": [ "# Getting Started\n", "\n", @@ -25,188 +24,214 @@ "The *builder*'s signatures look a bit crazy and full of generics that might be confusing, in reallity we add the generics so you can use any kind of type you neeed to use, if you want to do query filters by Records because you have a shared filters project, you can query by anonymous records as if you were using a *javascript* object as well it's your choice we try to stay as away as posible to let you decide how do you want to handle your data types.\n", "\n", "Let's start with some CRUD examples" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# Insert (Create)\n", "\n", "Inserts are fairly straight forward you just need to suply a sequence of objects they can be either records or anonymous records, if you want to go a little bit more dynamic you can also pass boxed objects that have different properties.\n", - "\n", - "" - ] + "\n" + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "#r \"nuget: Mondocks\"\n", - "\n", - "open Mondocks.Queries\n", - "\n", - "(* let's start by doing some Create operations \n", - " we'll try to show how can do mongo like constructs\n", - " and then you can add some type safety on top of that\n", - "*)\n", - "\n", - "let createSinglePost (title: string) (content: string) (author: string) = \n", - " insert \"posts\" {\n", - " documents\n", - " [\n", - " /// here we use anonymous records as if they were *mongo* (or *javascript*) objects\n", - " {| title = title; content = content; author = author |}\n", - " ]\n", - " }\n", - "let insertquery = createSinglePost \"This is my first post\" \"This is the content\" \"Angel D. Munoz\"\n", - "printfn $\"%s{insertquery}\"\n", - "" + "#!fsharp\r\n", + "#r \"nuget: Mondocks.Net\"\r\n", + "\r\n", + "open Mondocks.Queries\r\n", + "\r\n", + "(* let's start by doing some Create operations \r\n", + " we'll try to show how can do mongo like constructs\r\n", + " and then you can add some type safety on top of that\r\n", + "*)\r\n", + "\r\n", + "let createSinglePost (title: string) (content: string) (author: string) = \r\n", + " insert \"posts\" {\r\n", + " documents\r\n", + " [\r\n", + " /// here we use anonymous records as if they were *mongo* (or *javascript*) objects\r\n", + " {| title = title; content = content; author = author |}\r\n", + " ]\r\n", + " }\r\n", + "let insertquery = createSinglePost \"This is my first post\" \"This is the content\" \"Angel D. Munoz\"\r\n", + "printfn $\"%s{insertquery}\"\r\n" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"}]}" + "text/html": [ + "
Installed Packages
" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\r\n" + ] + }, + "metadata": {}, + "output_type": "display_data" } - ] + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + } + } }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* Now, what if you want to insert multiple values?\n", - " well creating a query for each is not efficient so you just need to\n", - " pass an IEnumerable, e.g. a list, an array or a sequence with the data you want to insert\n", - " but this makes things a litte bit weird so why don't we make a type for it?\n", - "*)\n", - "type InsertPost = {| title: string; content: string; author: string |}\n", - "\n", - "let createMultiplePosts (posts: InsertPost list) =\n", - " insert \"posts\" { documents posts }\n", - "\n", - "let postsToInsert =\n", - " createMultiplePosts [ {| title = \"This is my first post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\n", - " {| title = \"This is my second post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\n", - " {| title = \"This is my third post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\n", - " {| title = \"This is my fourth post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |} ]\n", - "\n", - "// let's compare both\n", - "printfn $\"%s{insertquery}\\n %s{postsToInsert}\"\n", - "" + "#!fsharp\r\n", + "(* Now, what if you want to insert multiple values?\r\n", + " well creating a query for each is not efficient so you just need to\r\n", + " pass an IEnumerable, e.g. a list, an array or a sequence with the data you want to insert\r\n", + " but this makes things a litte bit weird so why don't we make a type for it?\r\n", + "*)\r\n", + "type InsertPost = {| title: string; content: string; author: string |}\r\n", + "\r\n", + "let createMultiplePosts (posts: InsertPost list) =\r\n", + " insert \"posts\" { documents posts }\r\n", + "\r\n", + "let postsToInsert =\r\n", + " createMultiplePosts [ {| title = \"This is my first post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\r\n", + " {| title = \"This is my second post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\r\n", + " {| title = \"This is my third post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\r\n", + " {| title = \"This is my fourth post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |} ]\r\n", + "\r\n", + "// let's compare both\r\n", + "printfn $\"%s{insertquery}\\n %s{postsToInsert}\"\r\n" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"}]}" + "text/plain": [ + "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n " + "text/plain": [ + "\n", + " " + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my second post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my third post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my fourth post\"}]}" + "text/plain": [ + "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my second post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my third post\"},{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my fourth post\"}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ - "\n", - "" - ] + "\n" + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* Let's go very very dynamic in this example let's add objects that\n", - " have different kinds of properties, this might not be un-common if your database\n", - " is being filled with data coming from other languages like javascript or python\n", - "*)\n", - "\n", - "let createMultipleDynamicPosts = \n", - " insert \"posts\" {\n", - " documents [\n", - " box {| title = \"This is my first post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\n", - " box {| title = \"This is my second post\"; content = \"This is the content\"; author = \"Mike Thompson\"; createdAt = DateTime.Now |}\n", - " box {| title = \"This is my third post\"; content = \"This is the content\"; author = \"Frank Miller\"; tags = [ \"mongodb\"; \"fsharp\"; \"nosql\"] |}\n", - " ]\n", - " }\n", - "\n", + "#!fsharp\r\n", + "(* Let's go very very dynamic in this example let's add objects that\r\n", + " have different kinds of properties, this might not be un-common if your database\r\n", + " is being filled with data coming from other languages like javascript or python\r\n", + "*)\r\n", + "\r\n", + "let createMultipleDynamicPosts = \r\n", + " insert \"posts\" {\r\n", + " documents [\r\n", + " box {| title = \"This is my first post\"; content = \"This is the content\"; author = \"Angel D. Munoz\" |}\r\n", + " box {| title = \"This is my second post\"; content = \"This is the content\"; author = \"Mike Thompson\"; createdAt = DateTime.Now |}\r\n", + " box {| title = \"This is my third post\"; content = \"This is the content\"; author = \"Frank Miller\"; tags = [ \"mongodb\"; \"fsharp\"; \"nosql\"] |}\r\n", + " ]\r\n", + " }\r\n", + "\r\n", "printfn $\"%s{createMultipleDynamicPosts}\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"},{\"author\":\"Mike Thompson\",\"content\":\"This is the content\",\"createdAt\":{\"$date\":{\"$numberLong\":\"1608230189553\"}},\"title\":\"This is my second post\"},{\"author\":\"Frank Miller\",\"content\":\"This is the content\",\"tags\":[\"mongodb\",\"fsharp\",\"nosql\"],\"title\":\"This is my third post\"}]}" + "text/plain": [ + "{\"insert\":\"posts\",\"documents\":[{\"author\":\"Angel D. Munoz\",\"content\":\"This is the content\",\"title\":\"This is my first post\"},{\"author\":\"Mike Thompson\",\"content\":\"This is the content\",\"createdAt\":{\"$date\":{\"$numberLong\":\"1627430136402\"}},\"title\":\"This is my second post\"},{\"author\":\"Frank Miller\",\"content\":\"This is the content\",\"tags\":[\"mongodb\",\"fsharp\",\"nosql\"],\"title\":\"This is my third post\"}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "> Now I don't think I have to mention it but... that kind of dynamic data can be a little bit *problematic* if you try to use it from F# but business have different requirements all of the time as we mentioned earlier, we want to be as flexible as you need it to be as long as it fits mongo's way of doing things\n", "\n", "If you inspect a little the last output you'll see something that might not look like what you intended. Let's take a look for example at the `createdAt` property in the second post\n", "`\"createdAt\":{\"$date\":{\"$numberLong\":\"1608135821957\"}` this is what's called the [MongoDB](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) extended json spec and it's basically a way to represent binary data (remember MongoDB uses BSON not JSON) in json with the least amount of data loss" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# Find (Read)\n", "\n", @@ -214,582 +239,668 @@ "Also, remember that these filters must be written in the same way you would write them for mongo so you have to use `$in` `$or` and all of those operators, you can find that reference in the mongodb documentation about [Operators](https://docs.mongodb.com/manual/reference/operator/query/)\n", "\n", "Let's get onto it." - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "#r \"nuget: Mondocks\"\n", - "\n", - "open Mondocks.Queries\n", - "\n", - "(* First, let's do some 'raw' queries using anonymous records *)\n", - "\n", - "let productsCostAbovePrice (price: float) =\n", - " find \"products\" {\n", - " filter {| price = {| ``$gt``= price |} |}\n", - " }\n", - "\n", + "#!fsharp\r\n", + "#r \"nuget: Mondocks.Net\"\r\n", + "\r\n", + "open Mondocks.Queries\r\n", + "\r\n", + "(* First, let's do some 'raw' queries using anonymous records *)\r\n", + "\r\n", + "let productsCostAbovePrice (price: float) =\r\n", + " find \"products\" {\r\n", + " filter {| price = {| ``$gt``= price |} |}\r\n", + " }\r\n", + "\r\n", "printfn $\"%s{productsCostAbovePrice 10.25 }\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"products\",\"filter\":{\"price\":{\"$gt\":{\"$numberDouble\":\"10.25\"}}}}" + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "{\"find\":\"products\",\"filter\":{\"price\":{\"$gt\":{\"$numberDouble\":\"10.25\"}}}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } - ] + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + } + } }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* You can also leverage the dynamic nature of the anonymous\n", - " records to build filters on top of previous ones\n", - "*)\n", - "\n", - "type GreaterThanFloat = {| ``$gt``: float |}\n", - "type EqualsToInt = {| ``$eq``: int |}\n", - "type PriceFilter = {| price: GreaterThanFloat |}\n", - "type BatchNumFilter = {| batchNum: EqualsToInt |}\n", - "\n", - "\n", - "let greaterThanFloat (value: float): GreaterThanFloat = \n", - " {| ``$gt`` = value |}\n", - "\n", - "let equalsToInt (value: int): EqualsToInt = \n", - " {| ``$eq`` = value |}\n", - "\n", - "let getPriceFilter (gt: float -> GreaterThanFloat) (minPrice: float) =\n", - " {| price = gt minPrice |}\n", - "\n", - "let getBatchNumFilter (eq: int -> EqualsToInt) (batch: int) =\n", - " {| batchNum = eq batch |}\n", - "\n", - "// Let's combine the outputs from the previous functions to create a filter\n", - "// with both filters\n", - "let abovePriceFromBatch (minPrice: float) (batch: int) =\n", - " let priceFilter = getPriceFilter greaterThanFloat minPrice\n", - " {| priceFilter with batchNum = equalsToInt batch |}\n", - "\n", - "\n", - "let ``find products above 250.30`` = \n", - " find \"products\" {\n", - " filter (getPriceFilter greaterThanFloat 250.30)\n", - " }\n", - "\n", - "let ``find products from batch 10`` = \n", - " find \"products\" {\n", - " filter (getBatchNumFilter equalsToInt 10)\n", - " }\n", - "\n", - "let ``find products above 250.30 from batch 10`` =\n", - " find \"products\" {\n", - " filter (abovePriceFromBatch 250.30 10)\n", - " }\n", - "\n", + "#!fsharp\r\n", + "(* You can also leverage the dynamic nature of the anonymous\r\n", + " records to build filters on top of previous ones\r\n", + "*)\r\n", + "\r\n", + "type GreaterThanFloat = {| ``$gt``: float |}\r\n", + "type EqualsToInt = {| ``$eq``: int |}\r\n", + "type PriceFilter = {| price: GreaterThanFloat |}\r\n", + "type BatchNumFilter = {| batchNum: EqualsToInt |}\r\n", + "\r\n", + "\r\n", + "let greaterThanFloat (value: float): GreaterThanFloat = \r\n", + " {| ``$gt`` = value |}\r\n", + "\r\n", + "let equalsToInt (value: int): EqualsToInt = \r\n", + " {| ``$eq`` = value |}\r\n", + "\r\n", + "let getPriceFilter (gt: float -> GreaterThanFloat) (minPrice: float) =\r\n", + " {| price = gt minPrice |}\r\n", + "\r\n", + "let getBatchNumFilter (eq: int -> EqualsToInt) (batch: int) =\r\n", + " {| batchNum = eq batch |}\r\n", + "\r\n", + "// Let's combine the outputs from the previous functions to create a filter\r\n", + "// with both filters\r\n", + "let abovePriceFromBatch (minPrice: float) (batch: int) =\r\n", + " let priceFilter = getPriceFilter greaterThanFloat minPrice\r\n", + " {| priceFilter with batchNum = equalsToInt batch |}\r\n", + "\r\n", + "\r\n", + "let ``find products above 250.30`` = \r\n", + " find \"products\" {\r\n", + " filter (getPriceFilter greaterThanFloat 250.30)\r\n", + " }\r\n", + "\r\n", + "let ``find products from batch 10`` = \r\n", + " find \"products\" {\r\n", + " filter (getBatchNumFilter equalsToInt 10)\r\n", + " }\r\n", + "\r\n", + "let ``find products above 250.30 from batch 10`` =\r\n", + " find \"products\" {\r\n", + " filter (abovePriceFromBatch 250.30 10)\r\n", + " }\r\n", + "\r\n", "printf $\"{``find products above 250.30``}\\n{``find products from batch 10``}\\n{``find products above 250.30 from batch 10``}\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"products\",\"filter\":{\"price\":{\"$gt\":{\"$numberDouble\":\"250.3\"}}}}" + "text/plain": [ + "{\"find\":\"products\",\"filter\":{\"price\":{\"$gt\":{\"$numberDouble\":\"250.3\"}}}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n" + "text/plain": [ + "\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}}}}" + "text/plain": [ + "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}}}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n" + "text/plain": [ + "\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}},\"price\":{\"$gt\":{\"$numberDouble\":\"250.3\"}}}}" + "text/plain": [ + "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}},\"price\":{\"$gt\":{\"$numberDouble\":\"250.3\"}}}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* To do the usual pagination it's fairly simple as well *)\n", - "\n", - "let paginatedProductsPerBatch (batch: int) (page: int) (itemsPerPage: int) = \n", - " find \"products\" {\n", - " filter (getBatchNumFilter equalsToInt batch)\n", - " skip ((page - 1 ) * itemsPerPage)\n", - " limit itemsPerPage\n", - " }\n", - "\n", - "printfn $\"%s{paginatedProductsPerBatch 10 2 20}\"\n", - "" + "#!fsharp\r\n", + "(* To do the usual pagination it's fairly simple as well *)\r\n", + "\r\n", + "let paginatedProductsPerBatch (batch: int) (page: int) (itemsPerPage: int) = \r\n", + " find \"products\" {\r\n", + " filter (getBatchNumFilter equalsToInt batch)\r\n", + " skip ((page - 1 ) * itemsPerPage)\r\n", + " limit itemsPerPage\r\n", + " }\r\n", + "\r\n", + "printfn $\"%s{paginatedProductsPerBatch 10 2 20}\"\r\n" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}}},\"skip\":{\"$numberInt\":\"20\"},\"limit\":{\"$numberInt\":\"20\"}}" + "text/plain": [ + "{\"find\":\"products\",\"filter\":{\"batchNum\":{\"$eq\":{\"$numberInt\":\"10\"}}},\"skip\":{\"$numberInt\":\"20\"},\"limit\":{\"$numberInt\":\"20\"}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } - ] + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + } + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "If you want more type-safety around your queries you can use Records instead of anonymous ones, that'll allow you to (up to some degree) do some refactorings but please bear in mind that this only affects your queries, not the existing underlaying data inside your mongo database.\n", "Also, but you can still use records to *augment* your filters" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "open MongoDB.Bson\n", - "\n", - "type Question = { question: string; answer: string; }\n", - "type TitleFilter = { title: string }\n", - "type Form =\n", - " { _id: ObjectId\n", - " title: string;\n", - " questions: Question list\n", - " user: ObjectId }\n", - "\n", - "let filterQuestionsIn (questions: string list) =\n", - " let questions =\n", - " questions\n", - " |> List.map (fun question -> {| question = question |})\n", - " {| ``$in`` = questions |}\n", - "\n", - "let filterTitle (title: string) =\n", - " { title = title }\n", - "\n", - "let findFormsWithQuestions (questions: string list) =\n", - " find \"forms\" {\n", - " filter (filterQuestionsIn questions)\n", - " }\n", - "\n", - "let findFormsWithTitle (title: string) = \n", - " find \"forms\" {\n", - " filter (filterTitle title)\n", - " }\n", - "\n", - "let findFormsWithQuestionsAndTitle (questions: string list) (title: string) =\n", - " let formFilter =\n", - " let title = filterTitle title\n", - " // create a new filter from an existing record\n", - " {| title with \n", - " question = filterQuestionsIn questions |}\n", - " find \"forms\" {\n", - " filter formFilter\n", - " }\n", - "\n", - "let ``find forms with Sample as a title`` = \n", - " findFormsWithTitle \"Sample\"\n", - "\n", - "let ``find forms with two questions`` = \n", - " findFormsWithQuestions [\"What's your name?\"; \"Where do you live?\"]\n", - " \n", - "let ``find forms with two questions and Sample as a title`` = \n", - " findFormsWithQuestionsAndTitle [\"What's your name?\"; \"Where do you live?\"] \"Sample\"\n", - "\n", - "printfn $\"%s{``find forms with Sample as a title``}\\n%s{``find forms with two questions``}\\n%s{``find forms with two questions and Sample as a title``}\"\n", - "" + "#!fsharp\r\n", + "open MongoDB.Bson\r\n", + "\r\n", + "type Question = { question: string; answer: string; }\r\n", + "type TitleFilter = { title: string }\r\n", + "type Form =\r\n", + " { _id: ObjectId\r\n", + " title: string;\r\n", + " questions: Question list\r\n", + " user: ObjectId }\r\n", + "\r\n", + "let filterQuestionsIn (questions: string list) =\r\n", + " let questions =\r\n", + " questions\r\n", + " |> List.map (fun question -> {| question = question |})\r\n", + " {| ``$in`` = questions |}\r\n", + "\r\n", + "let filterTitle (title: string) =\r\n", + " { title = title }\r\n", + "\r\n", + "let findFormsWithQuestions (questions: string list) =\r\n", + " find \"forms\" {\r\n", + " filter (filterQuestionsIn questions)\r\n", + " }\r\n", + "\r\n", + "let findFormsWithTitle (title: string) = \r\n", + " find \"forms\" {\r\n", + " filter (filterTitle title)\r\n", + " }\r\n", + "\r\n", + "let findFormsWithQuestionsAndTitle (questions: string list) (title: string) =\r\n", + " let formFilter =\r\n", + " let title = filterTitle title\r\n", + " // create a new filter from an existing record\r\n", + " {| title with \r\n", + " question = filterQuestionsIn questions |}\r\n", + " find \"forms\" {\r\n", + " filter formFilter\r\n", + " }\r\n", + "\r\n", + "let ``find forms with Sample as a title`` = \r\n", + " findFormsWithTitle \"Sample\"\r\n", + "\r\n", + "let ``find forms with two questions`` = \r\n", + " findFormsWithQuestions [\"What's your name?\"; \"Where do you live?\"]\r\n", + " \r\n", + "let ``find forms with two questions and Sample as a title`` = \r\n", + " findFormsWithQuestionsAndTitle [\"What's your name?\"; \"Where do you live?\"] \"Sample\"\r\n", + "\r\n", + "printfn $\"%s{``find forms with Sample as a title``}\\n%s{``find forms with two questions``}\\n%s{``find forms with two questions and Sample as a title``}\"\r\n" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"forms\",\"filter\":{\"title\":\"Sample\"}}" + "text/plain": [ + "{\"find\":\"forms\",\"filter\":{\"title\":\"Sample\"}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n" + "text/plain": [ + "\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"forms\",\"filter\":{\"$in\":[{\"question\":\"What\\u0027s your name?\"},{\"question\":\"Where do you live?\"}]}}" + "text/plain": [ + "{\"find\":\"forms\",\"filter\":{\"$in\":[{\"question\":\"What\\u0027s your name?\"},{\"question\":\"Where do you live?\"}]}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n" + "text/plain": [ + "\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"find\":\"forms\",\"filter\":{\"question\":{\"$in\":[{\"question\":\"What\\u0027s your name?\"},{\"question\":\"Where do you live?\"}]},\"title\":\"Sample\"}}" + "text/plain": [ + "{\"find\":\"forms\",\"filter\":{\"question\":{\"$in\":[{\"question\":\"What\\u0027s your name?\"},{\"question\":\"Where do you live?\"}]},\"title\":\"Sample\"}}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# Update\n", "\n", "Update operations are fairly straight forward as well although, for the update queries there's a helper type that can be used named `UpdateQuery<'Query, 'Update, 'Hint>` which contains all of the availabe properties for the `update` document\n", "\n", "let's dive into it" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "#r \"nuget: Mondocks\"\n", - "\n", - "open MongoDB.Bson\n", - "open Mondocks.Types\n", - "open Mondocks.Queries\n", - "\n", - "type User = { _id: ObjectId; name: string; age: int; email: string; }\n", - "\n", - "let updateUsersEmail(payload: User * string) =\n", - " let (user, email) = payload\n", - " let updatedUser = { user with email = email }\n", - " update \"users\" {\n", - " updates [\n", - " { q = {| _id = user._id |}; \n", - " u = updatedUser; \n", - " upsert = None; multi = None; \n", - " collation = None; \n", - " arrayFilters = None; \n", - " hint = None }\n", - " ]\n", - " }\n", - "\n", - "/// Now, let's say for example further down the road you need to add a property to all of your existing users\n", - "/// think of this as a \"migration\" and setting a default value so it doesn't conflict with\n", - "/// your existing F# records\n", - "\n", - "type UserUpgrade = { _id: ObjectId; name: string; age: int; email: string; withNewProperty: bool }\n", - "\n", - "let upgradeUsersSchema =\n", - " update \"users\" {\n", - " updates [\n", - " { q = {||};\n", - " u = {| ``$set`` = {| withNewProperty = true |} |}\n", - " upsert = None\n", - " multi = Some true\n", - " collation = None\n", - " arrayFilters = None\n", - " hint = None }\n", - " ]\n", - " }\n", - "\n", - "let user = {_id = ObjectId.GenerateNewId(); name = \"Daniel\"; age = 28; email= \"some@email.com\" }\n", - "let newEmail = \"new@email.com\"\n", - "printfn $\"%s{updateUsersEmail (user, newEmail)}\\n%s{upgradeUsersSchema}\"\n", - "" + "#!fsharp\r\n", + "#r \"nuget: Mondocks.Net\"\r\n", + "\r\n", + "open MongoDB.Bson\r\n", + "open Mondocks.Types\r\n", + "open Mondocks.Queries\r\n", + "\r\n", + "type User = { _id: ObjectId; name: string; age: int; email: string; }\r\n", + "\r\n", + "let updateUsersEmail(payload: User * string) =\r\n", + " let (user, email) = payload\r\n", + " let updatedUser = { user with email = email }\r\n", + " update \"users\" {\r\n", + " updates [\r\n", + " { q = {| _id = user._id |}; \r\n", + " u = updatedUser; \r\n", + " upsert = None; multi = None; \r\n", + " collation = None; \r\n", + " arrayFilters = None; \r\n", + " hint = None }\r\n", + " ]\r\n", + " }\r\n", + "\r\n", + "/// Now, let's say for example further down the road you need to add a property to all of your existing users\r\n", + "/// think of this as a \"migration\" and setting a default value so it doesn't conflict with\r\n", + "/// your existing F# records\r\n", + "\r\n", + "type UserUpgrade = { _id: ObjectId; name: string; age: int; email: string; withNewProperty: bool }\r\n", + "\r\n", + "let upgradeUsersSchema =\r\n", + " update \"users\" {\r\n", + " updates [\r\n", + " { q = {||};\r\n", + " u = {| ``$set`` = {| withNewProperty = true |} |}\r\n", + " upsert = None\r\n", + " multi = Some true\r\n", + " collation = None\r\n", + " arrayFilters = None\r\n", + " hint = None }\r\n", + " ]\r\n", + " }\r\n", + "\r\n", + "let user = {_id = ObjectId.GenerateNewId(); name = \"Daniel\"; age = 28; email= \"some@email.com\" }\r\n", + "let newEmail = \"new@email.com\"\r\n", + "printfn $\"%s{updateUsersEmail (user, newEmail)}\\n%s{upgradeUsersSchema}\"\r\n" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"update\":\"users\",\"updates\":[{\"q\":{\"_id\":{\"$oid\":\"5fdba52d6e3b4e6f666b1cc3\"}},\"u\":{\"_id\":{\"$oid\":\"5fdba52d6e3b4e6f666b1cc3\"},\"name\":\"Daniel\",\"age\":{\"$numberInt\":\"28\"},\"email\":\"new@email.com\"}}]}" + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "{\"update\":\"users\",\"updates\":[{\"q\":{\"_id\":{\"$oid\":\"61009d133421798f68331961\"}},\"u\":{\"_id\":{\"$oid\":\"61009d133421798f68331961\"},\"name\":\"Daniel\",\"age\":{\"$numberInt\":\"28\"},\"email\":\"new@email.com\"}}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\n" + "text/plain": [ + "\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "{\"update\":\"users\",\"updates\":[{\"q\":{},\"u\":{\"$set\":{\"withNewProperty\":true}},\"multi\":true}]}" + "text/plain": [ + "{\"update\":\"users\",\"updates\":[{\"q\":{},\"u\":{\"$set\":{\"withNewProperty\":true}},\"multi\":true}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } - ] + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + } + } }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* In version 0.3.15 of Mondocks we decided to relax a little bit the updates\n", - " you can pass a boxed sequence of anonymous records with only the properties you want \n", - " this allows you to skip other properties that are present inside the \n", - " `UpdateQuery` record\n", - "*)\n", - "\n", - "let upgradeUsersSchema =\n", - " update \"users\" {\n", - " updates [\n", - " box {| q = {||}\n", - " u = {| ``$set`` = {| withNewProperty = true |} |}\n", - " multi = true |}\n", - " ]\n", - " }\n", + "#!fsharp\r\n", + "(* In version 0.3.15 of Mondocks we decided to relax a little bit the updates\r\n", + " you can pass a boxed sequence of anonymous records with only the properties you want \r\n", + " this allows you to skip other properties that are present inside the \r\n", + " `UpdateQuery` record\r\n", + "*)\r\n", + "\r\n", + "let upgradeUsersSchema =\r\n", + " update \"users\" {\r\n", + " updates [\r\n", + " box {| q = {||}\r\n", + " u = {| ``$set`` = {| withNewProperty = true |} |}\r\n", + " multi = true |}\r\n", + " ]\r\n", + " }\r\n", "printfn $\"{upgradeUsersSchema}\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"update\":\"users\",\"updates\":[{\"multi\":true,\"q\":{},\"u\":{\"$set\":{\"withNewProperty\":true}}}]}" + "text/plain": [ + "{\"update\":\"users\",\"updates\":[{\"multi\":true,\"q\":{},\"u\":{\"$set\":{\"withNewProperty\":true}}}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "### BEWARE\n", "executing queries against `{||}` (mongo's `{}`) is going to affect all documents inside a collection (specially if `multi = Some true` is provided)\n", "\n", "So be really careful when you update your database using empty objects " - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "metadata": {}, "source": [ "# Delete\n", "\n", "Deletes are very much like Updates there's also a helper type named `DeleteQuery<'Delete, 'Hint, 'Comment>` which contains all of the available properties for the `delete` document\n", "\n", "Let's check some samples" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "#r \"nuget: Mondocks\"\n", - "\n", - "open System\n", - "open Mondocks.Queries\n", - "\n", - "let deleteUserWhereName (name: string) =\n", - " delete \"users\" {\n", - " deletes [\n", - " { q = {| name = name |}\n", - " /// if you want to delete all documents that match the criteria use\n", - " /// `limit = 0`\n", - " limit = 1\n", - " collation = None\n", - " hint = None\n", - " comment = None }\n", - " ]\n", - " }\n", - "" - ], - "outputs": [] + "#!fsharp\r\n", + "#r \"nuget: Mondocks\"\r\n", + "\r\n", + "open System\r\n", + "open Mondocks.Queries\r\n", + "\r\n", + "let deleteUserWhereName (name: string) =\r\n", + " delete \"users\" {\r\n", + " deletes [\r\n", + " { q = {| name = name |}\r\n", + " /// if you want to delete all documents that match the criteria use\r\n", + " /// `limit = 0`\r\n", + " limit = 1\r\n", + " collation = None\r\n", + " hint = None\r\n", + " comment = None }\r\n", + " ]\r\n", + " }\r\n" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + } + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "> You can do multiple delete queries (with updates as well)\n", ">\n", "> ***NOTE***: Please note that you need to box the queries if they have different matching criteria" - ] + ], + "metadata": {} }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "let deleteAllUsersWhereName (name: string) =\n", - " { q = box {| name = name |}\n", - " limit = 0\n", - " collation = None\n", - " hint = None\n", - " comment = None }\n", - "\n", - "let delete10YOStaleUsers() =\n", - " { q = \n", - " // sorry if my query is incorrect I just want to showcase multiple deletes 😅\n", - " box {| lastLogin = {| ``$lte`` = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3650.)) |} |}\n", - " limit = 0\n", - " collation = None\n", - " hint = None\n", - " comment = None }\n", - "\n", - "let deleteAllStaleUsersFromDate = \n", - " delete \"users\" {\n", - " deletes [\n", - " deleteAllUsersWhereName \"Angel\"\n", - " delete10YOStaleUsers()\n", - " ]\n", - " }\n", - "\n", + "#!fsharp\r\n", + "let deleteAllUsersWhereName (name: string) =\r\n", + " { q = box {| name = name |}\r\n", + " limit = 0\r\n", + " collation = None\r\n", + " hint = None\r\n", + " comment = None }\r\n", + "\r\n", + "let delete10YOStaleUsers() =\r\n", + " { q = \r\n", + " // sorry if my query is incorrect I just want to showcase multiple deletes 😅\r\n", + " box {| lastLogin = {| ``$lte`` = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3650.)) |} |}\r\n", + " limit = 0\r\n", + " collation = None\r\n", + " hint = None\r\n", + " comment = None }\r\n", + "\r\n", + "let deleteAllStaleUsersFromDate = \r\n", + " delete \"users\" {\r\n", + " deletes [\r\n", + " deleteAllUsersWhereName \"Angel\"\r\n", + " delete10YOStaleUsers()\r\n", + " ]\r\n", + " }\r\n", + "\r\n", "printfn $\"%s{deleteAllStaleUsersFromDate}\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"delete\":\"users\",\"deletes\":[{\"q\":{\"name\":\"Angel\"},\"limit\":{\"$numberInt\":\"0\"}},{\"q\":{\"lastLogin\":{\"$lte\":{\"$date\":{\"$numberLong\":\"1292870189947\"}}}},\"limit\":{\"$numberInt\":\"0\"}}]}" + "text/plain": [ + "{\"delete\":\"users\",\"deletes\":[{\"q\":{\"name\":\"Angel\"},\"limit\":{\"$numberInt\":\"0\"}},{\"q\":{\"lastLogin\":{\"$lte\":{\"$date\":{\"$numberLong\":\"1312070174027\"}}}},\"limit\":{\"$numberInt\":\"0\"}}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, "source": [ - "#!fsharp\n", - "(* In version 0.3.15 of Mondocks we decided to relax a little bit the deletes\n", - " you can pass a boxed sequence of anonymous records with only the properties you want \n", - " this allows you to skip other properties that are present inside the \n", - " `DeleteQuery` record\n", - "*)\n", - "\n", - "let relaxedDeletes = \n", - " delete \"users\" {\n", - " deletes [\n", - " // deletes all of the users named Angel\n", - " box {| q = {| name = \"Angel\" |}; limt = 0 |}\n", - " box {| q = {| name = \"Peter\" |}; limt = 4 |}\n", - " box {| q = {| name = \"Frank\" |}; limt = 1 |}\n", - " ]\n", - " }\n", + "#!fsharp\r\n", + "(* In version 0.3.15 of Mondocks we decided to relax a little bit the deletes\r\n", + " you can pass a boxed sequence of anonymous records with only the properties you want \r\n", + " this allows you to skip other properties that are present inside the \r\n", + " `DeleteQuery` record\r\n", + "*)\r\n", + "\r\n", + "let relaxedDeletes = \r\n", + " delete \"users\" {\r\n", + " deletes [\r\n", + " // deletes all of the users named Angel\r\n", + " box {| q = {| name = \"Angel\" |}; limt = 0 |}\r\n", + " box {| q = {| name = \"Peter\" |}; limt = 4 |}\r\n", + " box {| q = {| name = \"Frank\" |}; limt = 1 |}\r\n", + " ]\r\n", + " }\r\n", "printfn $\"%s{relaxedDeletes}\"" ], "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": "{\"delete\":\"users\",\"deletes\":[{\"limt\":{\"$numberInt\":\"0\"},\"q\":{\"name\":\"Angel\"}},{\"limt\":{\"$numberInt\":\"4\"},\"q\":{\"name\":\"Peter\"}},{\"limt\":{\"$numberInt\":\"1\"},\"q\":{\"name\":\"Frank\"}}]}" + "text/plain": [ + "{\"delete\":\"users\",\"deletes\":[{\"limt\":{\"$numberInt\":\"0\"},\"q\":{\"name\":\"Angel\"}},{\"limt\":{\"$numberInt\":\"4\"},\"q\":{\"name\":\"Peter\"}},{\"limt\":{\"$numberInt\":\"1\"},\"q\":{\"name\":\"Frank\"}}]}" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": "\r\n" + "text/plain": [ + "\r\n" + ] }, - "execution_count": 1, - "metadata": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "metadata": { + "dotnet_interactive": { + "language": "fsharp" } - ] + } } ], "metadata": {