Add quick start section to readme

This commit is contained in:
Paul Brinkmeier 2023-09-16 19:52:35 +02:00
parent d54cf6ea7f
commit 423f28f0a2
3 changed files with 67 additions and 3 deletions

View File

@ -2,14 +2,66 @@
> An opionated Haskell Postgres library. > An opionated Haskell Postgres library.
## Quick Start
We assume that our database contains this table:
```sql
CREATE TABLE person (
name TEXT NOT NULL,
age INT NOT NULL,
score DOUBLE PRECISION NOT NULL,
motto TEXT
)
```
We can use `opium` to decode query results into Haskell data types:
```haskell
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import GHC.Generics (Generic)
import Database.PostgreSQL.LibPQ (Connection)
import qualified Database.PostgreSQL.Opium as Opium
data User = User
{ name :: String
, age :: Int
, score :: Double
} deriving (Eq, Generic, Show)
instance Opium.FromRow User where
getUsers :: Connection -> IO (Either Opium.Error [Users])
getUsers conn = Opium.fetch_ conn "SELECT * FROM user"
```
The `Opium.FromRow` instance is implemented generically for all product types ("records"). It looks up the field name in the query result and decodes the column value using `Opium.FromField`.
The intended use case for this library is to enable us to write ad-hoc types for query results without having to manually write instances.
For example, if we wanted to figure out how user age influences their score:
```haskell
data ScoreByAge = ScoreByAge { t :: Double, m :: Double }
deriving (Eq, Generic, Show)
getScoreByAge :: Connection -> IO ScoreByAge
getScoreByAge conn = do
let query = "SELECT regr_intercept(score, age) AS t, regr_slope(score, age) AS m FROM user"
Right [x] <- Opium.fetch_ conn query
pure x
```
## TO DO ## TO DO
- [x] Implement `String` and `Text` decoding - [x] Implement `String` and `Text` decoding
- [x] Implement `Int` decoding - [x] Implement `Int` decoding
- [x] Implement error reporting i.e. use `Either OpiumError` instead of `Maybe` - [x] Implement error reporting i.e. use `Either OpiumError` instead of `Maybe`
- [x] Implement `Float` and `Double` decoding - [x] Implement `Float` and `Double` decoding
- [x] Clean up and document column table stuff
- [ ] Implement `fetch` (`fetch_` but with parameter passing)
- [ ] Implement `UTCTime` and zoned time decoding - [ ] Implement `UTCTime` and zoned time decoding
- [ ] Implement JSON decoding - [ ] Implement JSON decoding
- [ ] Implement `ByteString` decoding (`bytea`) - [ ] Implement `ByteString` decoding (`bytea`)
- Can we make the fromField instance choose whether it wants binary or text? - Can we make the fromField instance choose whether it wants binary or text?
- [x] Clean up and document column table stuff

View File

@ -1,5 +1,6 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
@ -38,6 +39,13 @@ data ManyFields = ManyFields
instance Opium.FromRow ManyFields where instance Opium.FromRow ManyFields where
data ScoreByAge = ScoreByAge
{ m :: Double
, t :: Double
} deriving (Eq, Generic, Show)
instance Opium.FromRow ScoreByAge where
isLeft :: Either a b -> Bool isLeft :: Either a b -> Bool
isLeft (Left _) = True isLeft (Left _) = True
isLeft _ = False isLeft _ = False
@ -125,3 +133,7 @@ spec = do
it "Fails for the wrong column type" $ \conn -> do it "Fails for the wrong column type" $ \conn -> do
rows <- Opium.fetch_ @Person conn "SELECT 'quby' AS name, 'indeterminate' AS age" rows <- Opium.fetch_ @Person conn "SELECT 'quby' AS name, 'indeterminate' AS age"
rows `shouldBe` Left (Opium.ErrorInvalidOid "age" $ LibPQ.Oid 25) rows `shouldBe` Left (Opium.ErrorInvalidOid "age" $ LibPQ.Oid 25)
it "Works for the readme regression example" $ \conn -> do
rows <- Opium.fetch_ @ScoreByAge conn "SELECT regr_intercept(score, age) AS t, regr_slope(score, age) AS m FROM person"
rows `shouldSatisfy` \case { (Right [ScoreByAge _ _]) -> True; _ -> False }

View File

@ -23,8 +23,8 @@ setupConnection = do
conn <- LibPQ.connectdb $ Encoding.encodeUtf8 $ Text.pack dsn conn <- LibPQ.connectdb $ Encoding.encodeUtf8 $ Text.pack dsn
_ <- LibPQ.setClientEncoding conn "UTF8" _ <- LibPQ.setClientEncoding conn "UTF8"
_ <- LibPQ.exec conn "CREATE TABLE person (name TEXT NOT NULL, age INT NOT NULL, motto TEXT)" _ <- LibPQ.exec conn "CREATE TABLE person (name TEXT NOT NULL, age INT NOT NULL, score DOUBLE PRECISION NOT NULL, motto TEXT)"
_ <- LibPQ.exec conn "INSERT INTO person VALUES ('paul', 25), ('albus', 103)" _ <- LibPQ.exec conn "INSERT INTO person VALUES ('paul', 25, 30), ('albus', 103, 50.42)"
pure conn pure conn