87 lines
2.9 KiB
Markdown
87 lines
2.9 KiB
Markdown
# opium
|
|
|
|
> 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 [User])
|
|
getUsers = Opium.fetch_ "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)
|
|
|
|
instance Opium.FromRow ScoreByAge where
|
|
|
|
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 (Identity x) <- Opium.fetch_ query conn
|
|
pure x
|
|
```
|
|
|
|
## TO DO
|
|
|
|
- [x] Implement `String` and `Text` decoding
|
|
- [x] Implement `Int` decoding
|
|
- [x] Implement error reporting i.e. use `Either OpiumError` instead of `Maybe`
|
|
- [x] Implement `Float` and `Double` decoding
|
|
- [x] Clean up and document column table stuff
|
|
- [x] Decode `LibPQ.Binary`
|
|
- [x] Implement `date -> Day` decoding
|
|
- [x] Implement `UTCTime`
|
|
- [x] Implement `ByteString` decoding (`bytea`)
|
|
- [x] Test negative integer decoding, especially for `Integer`
|
|
- [ ] Implement time intervals
|
|
- [ ] and zoned time decoding
|
|
- [ ] How about `timezone`? This could prove problematic when the server and application have different time zones
|
|
- [x] Implement `fetch` (`fetch_` but with parameter passing)
|
|
- [ ] Implement JSON decoding
|
|
- [ ] Implement (anonymous) composite types
|
|
- [ ] Catch [UnicodeException](https://hackage.haskell.org/package/text-2.1/docs/Data-Text-Encoding-Error.html#t:UnicodeException) when decoding text
|
|
- This might not be necessary if Postgres guarantees us that having a textual OID on a field means that the field is encoded correctly.
|
|
- [ ] Implement array decoding
|
|
- [ ] Better docs and structure for `FromRow` module
|
|
- [ ] Lexer for PostgreSQL that replaces $name by $1, $2, etc.
|
|
- [ ] Tutorial
|
|
- [ ] Rationale
|
|
- [ ] `FromRow`
|
|
- [ ] Custom `FromField` impls
|
|
- [ ] Improve type errors when trying to `instance` a type that isn't a record (e.g. sum type)
|
|
- [ ] Improve documentation for `fromRow` module
|