47
loading...
This website collects cookies to deliver better user experience
As in previous entries I'll be using F# scripts which can be executed with the .NET CLI that comes in the .NET SDK which you can get from here: https://get.dot.net
If you have docker installed, spin up a postgresql instance
docker run -d \
--name my-instance-name \
-e POSTGRES_PASSWORD=Admin123 \
-e POSTGRES_USER=admin
-p 5432:5432 \
postgres:alpine
create table authors(
id uuid primary key,
name varchar(100),
email varchar(100),
twitter_handle varchar(100) null
);
create table posts(
id uuid primary key,
title varchar(140) not null,
content text not null,
authorId uuid references authors(id)
);
You can create the PostgreSQL database using any DB manager you already know. In case you don't have anything available you can use dbeaver.
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
type Post =
{ id: Guid
title: string
content: string
authorId: Guid }
// From F# 5.0 + you can "require" NuGet packages in F# scripts
#r "nuget: Npgsql"
#r "nuget: Dapper.FSharp"
open System
open Npgsql
open Dapper.FSharp
open Dapper.FSharp.PostgreSQL
// register our optional F# types
OptionTypes.register ()
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
// register our tables
let authorTable =
// we can use this function to match tables
// with different names to our record definitions
table'<Author> "authors" |> inSchema "public"
let connstring =
"Host=192.168.100.5;Username=admin;Password=Admin123;Database=simple_fsharp"
/// In normal circunstances you would write
/// `use! conn = new NpgsqlConnection(connString)`
/// but inside F# scripts we're not allowed for top declarations like this,
/// so we use let instead
let conn = new NpgsqlConnection(connstring)
// Generate two different authors
// one with an optional handle to see how we can deal with null values
let authors =
[ { id = Guid.NewGuid()
name = "Angel D. Munoz"
email = "[email protected]"
twitter_handle = Some "angel_d_munoz" }
{ id = Guid.NewGuid()
name = "Misterious Person"
email = "[email protected]"
twitter_handle = None } ]
// If you were to use ASP.NET core
// you would be running on a task or async method
task {
/// the `!` here indicates that we will wait
/// for the `InsertAsync` operation to finish
let! result =
// here's the Dapper.FSharp magical DSL
insert {
into authorTable
values authors
}
|> conn.InsertAsync
/// If all goes well you should see
/// `Rows Affected: 2` in your console
printfn $"Rows Affected: %i{result}"
}
// we're inside a script hence why we need run it synchronously
// most of the time you don't need this
|> Async.AwaitTask
|> Async.RunSynchronously
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
If you get a message like "warning FS3511: This state machine is not statically compilable." don't worry it is being tracked in https://github.com/dotnet/fsharp/issues/12038
#r "nuget: Dapper.FSharp"
#r "nuget: Npgsql"
open System
open Npgsql
open Dapper.FSharp
open Dapper.FSharp.PostgreSQL
// register our optional F# types
OptionTypes.register ()
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
let authorTable =
table'<Author> "authors" |> inSchema "public"
let connstring =
"Host=192.168.100.5;Username=admin;Password=Admin123;Database=simple_fsharp"
let conn = new NpgsqlConnection(connstring)
task {
let! allUsers =
select {
for author in authorTable do
selectAll
}
|> conn.SelectAsync<Author>
printfn "Names: "
for user in allUsers do
printfn $"\t%s{user.name}"
let! usersWithTwitterHandle =
select {
for author in authorTable do
where (author.twitter_handle <> None)
}
|> conn.SelectAsync<Author>
printfn "Twitter Handles:"
for user in usersWithTwitterHandle do
// we use .Value because filter users whose handle is None
printfn $"\t%s{user.twitter_handle.Value}"
}
// we're inside a script hence why we need run it synchronously
// most of the time you don't need this
|> Async.AwaitTask
|> Async.RunSynchronously
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
Names:
Angel D. Munoz
Misterious Person
Twitter Handles:
angel_d_munoz
#r "nuget: Dapper.FSharp"
#r "nuget: Npgsql"
open System
open Npgsql
open Dapper.FSharp
open Dapper.FSharp.PostgreSQL
// register our optional F# types
OptionTypes.register ()
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
// register our tables
let authorTable =
table'<Author> "authors" |> inSchema "public"
let connstring =
"Host=192.168.100.5;Username=admin;Password=Admin123;Database=simple_fsharp"
let conn = new NpgsqlConnection(connstring)
task {
let! noHandleUsers =
select {
for author in authorTable do
where (author.twitter_handle = None)
}
|> conn.SelectAsync<Author>
// let's try to get the first result from the result set
match noHandleUsers |> Seq.tryHead with
// if there is one, let's update it
| Some user ->
let user =
// partially update the record of the user with
// the F# record update syntax
{ user with twitter_handle = Some "mysterious_fsharper" }
let! result =
update {
for author in authorTable do
set user
where (author.id = user.id)
}
|> conn.UpdateAsync
printfn $"Users updated: %i{result}"
// if we have run this script, our result set will be empty
| None -> printfn "No Users Without handle were Found"
}
// we're inside a script hence why we need run it synchronously
// most of the time you don't need this
|> Async.AwaitTask
|> Async.RunSynchronously
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
Users updated: 1
No Users Without handle were Found
mysterious_fsharper
handleTwitter Handles:
angel_d_munoz
mysterious_fsharper
Author
and Post
records a little bit, we will add a static function called DataReader
which will take an IDataReader
and return the corresponding record#r "nuget: Donald"
open System
open System.Data
open Donald
// Same Author model from before
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
// Add the DataReader
static member DataReader(rd: IDataReader) : Author =
// the reader has some functions that help us map
// existing columns from the database and their
// data type to our record, this can be really great
// when you need to work on a schema you don't own
{ id = rd.ReadGuid "id"
name = rd.ReadString "name"
email = rd.ReadString "email"
twitter_handle = rd.ReadStringOption "twitter_handle" }
// We do the same with the Post record
type Post =
{ id: Guid
title: string
content: string
authorId: Guid }
static member DataReader(rd: IDataReader) : Post =
{ id = rd.ReadGuid "id"
title = rd.ReadString "title"
content = rd.ReadString "content"
authorId = rd.ReadGuid "authorId" }
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
There are more patterns you can follow rather than attaching the static function directly to the Record, you could have a module Author = ...
which contains helper functions (like the data reader) but for simplicity we will attach it right there in the record.
|>
), this is similar to other fluent APIs in other languages like db.newCommand(cmd).setParams(params).execute()
these are common in C#, in the case of F# we do something like that but using individual functions and partial application in some caseslet authorsFluent =
conn
|> Db.newCommand "SELECT * FROM authors WHERE twitter_handle <> @handle"
|> Db.setParams [ "handle", SqlType.Null ]
|> Db.query Author.DataReader
Computation Expressions
which you already used with Dapper.FSharp! Here's the same previous query with the expression stylelet authorsExpression =
dbCommand conn {
cmdText "SELECT * FROM authors WHERE twitter_handle <> @handle"
cmdParam [ "handle", SqlType.Null ]
}
|> Db.query Author.DataReader
#r "nuget: Npgsql"
#r "nuget: Donald"
open System
open Npgsql
open Donald
open System.Data
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
static member DataReader(rd: IDataReader) : Author =
{ id = rd.ReadGuid "id"
name = rd.ReadString "name"
email = rd.ReadString "email"
twitter_handle = rd.ReadStringOption "twitter_handle" }
type Post =
{ id: Guid
title: string
content: string
authorId: Guid }
static member DataReader(rd: IDataReader) : Post =
{ id = rd.ReadGuid "id"
title = rd.ReadString "title"
content = rd.ReadString "content"
authorId = rd.ReadGuid "authorId" }
let connstring =
"Host=192.168.100.5;Username=admin;Password=Admin123;Database=simple_fsharp"
let conn = new NpgsqlConnection(connstring)
let authorsResult =
// let's query all of the authors
dbCommand conn { cmdText "SELECT * FROM authors" }
|> Db.query Author.DataReader
let authors =
// authorsResult is a DbResult<Author list>
// that is a helper type
// which help us successful and failed database operations
match authorsResult with
// if the operation was successful return the authors
| Ok authors -> authors
// otherwise print to the console what failed
// and return an empty list
| Error err ->
printfn "%O" err
List.empty
let insertCommand =
"""INSERT INTO posts(id, title, content, authorId)
VALUES(@id, @title, @content, @authorId)"""
for author in authors do
let postId = Guid.NewGuid()
let result =
dbCommand conn {
cmdText insertCommand
cmdParam [ "id", SqlType.Guid postId
"title", SqlType.String $"RandomPost: {postId}"
"content", SqlType.String "This is an extremely Long Post!..."
"authorId", SqlType.Guid author.id ]
}
|> Db.exec
match result with
| Ok () -> printfn $"Inserted post with id: {postId}"
| Error err -> printfn $"Failed to insert post with id: {postId}... {err}"
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
Time for some shameless plug (with some shame)
#r "nuget: Mondocks.Net"
#r "nuget: MongoDB.Driver"
open System
open MongoDB
open MongoDB.Driver
open Mondocks.Queries
open Mondocks.Types
type Author =
{ id: Guid
name: string
email: string
twitter_handle: string option }
type Post =
{ id: Guid
title: string
content: string
authorId: Guid }
let insertCmd =
insert "authors" {
documents [ { id = Guid.NewGuid()
name = "Angel D. Munoz"
email = "[email protected]"
twitter_handle = Some "angel_d_munoz" }
{ id = Guid.NewGuid()
name = "Misterious Person"
email = "[email protected]"
twitter_handle = None } ]
}
let client = MongoClient("mongodb://192.168.100.5/")
let database = client.GetDatabase("simple_fsharp")
let result =
database.RunCommand<InsertResult>(JsonCommand insertCmd)
printfn $"Inserted: %i{result.n}"
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx
#r "nuget: Mondocks.Net"
#r "nuget: MongoDB.Driver"
open System
open MongoDB.Bson
open MongoDB.Driver
open Mondocks.Queries
open Mondocks.Types
type Author =
{ _id: Guid
name: string
email: string
twitter_handle: string }
let client = MongoClient("mongodb://192.168.100.5/")
let database = client.GetDatabase("simple_fsharp")
let findCmd =
find "authors" {
filter {| twitter_handle = "" |}
limit 1
}
let result =
database.RunCommand<FindResult<Author>>(JsonCommand findCmd)
// check on the database result set if we have an author
match result.cursor.firstBatch |> Seq.tryHead with
| Some author ->
let updateCmd =
update "authors" {
// query by author _id
updates [ { q = {| _id = author._id |}
u =
{ author with
// set the updated handle
twitter_handle = "mysterious_fsharper" }
multi = Some false
upsert = Some false
collation = None
arrayFilters = None
hint = None } ]
}
let result =
database.RunCommand<UpdateResult>(JsonCommand updateCmd)
printfn $"Updated: %i{result.n}"
| None -> printfn "No Author was found"
To Run this, copy this content into a file named script.fsx
(or whatever name you prefer) and type:
dotnet fsi script.fsx