dylex / postgresql-typed

Haskell PostgreSQL library with compile-time type inference
http://hackage.haskell.org/package/postgresql-typed
Other
84 stars 12 forks source link

Could you provide a simple example for Haskell noobs like me? ;) #29

Closed benjamin-thomas closed 2 years ago

benjamin-thomas commented 2 years ago

Just a simple code block on the README would be of great help.

I'd like to evaluate your library against the Rust sqlx library which has similar type safe garantees and which I'm also testing by following along a book here: https://github.com/benjamin-thomas/zero2prod

I did manage to evaluate postgresql-simple alright here but without type safety that's a meh for me :(

After exporting the proper env vars, I do see typing related traffic to my dev database, however I'm not sure why the following code won't compile:

module Main (main) where

import Database.PostgreSQL.Typed

data Client = Client {id_ :: Int, name :: String}
    deriving (Show)

newClient :: Int -> String -> Client
newClient id2 name2 = Client{id_ = id2, name = name2}

selectClients :: Int -> PGConnection -> IO [Client]
selectClients clientID postgresConn = do
    rows <-
        pgQuery
            postgresConn
            [pgSQL|SELECT id, name FROM clients WHERE id = ${clientID} ORDER BY id ASC|]
    pure (fmap clientFromRow rows)

clientFromRow :: (Int, String) -> Client
clientFromRow (a, b) = newClient a b

main :: IO ()
main = do
    putStrLn "WIP"
app/Main.hs:60:58: error: parse error on input ‘=’
   |
60 |             [pgSQL|SELECT id, name FROM clients WHERE id = ${userId} ORDER BY id ASC |]
me@dev:~/code/explore/haskell/pg-haskell$ cat ./pg-haskell.cabal | grep Ext
    default-Extensions: TemplateHaskell
dylex commented 2 years ago

That indeed sounds like a great idea. There is a sort of example in https://github.com/dylex/postgresql-typed/blob/master/test/Main.hs but it's probably a bit convoluted.

First guess, you also need to enable the QuasiQuotes language extension along with TemplateHaskell (that's what the [qq| |] construction is called).

dylex commented 2 years ago

I added a bit to the README, see what you think

benjamin-thomas commented 2 years ago

I added a bit to the README, see what you think

Thanks a ton, it really helped. I did have a look at the tests but it was a bit too much for me on my first day with Haskell ;)

I suggest putting all code into Main.hs to make things even simpler to try out.

And also add an alternative for TCP connections so that one can just swap out the argument for pgConnect easily.

For this to work, I found I had to bring in 2 additional dependencies:

build-depends:    base ^>=4.14.3.0,
                  postgresql-typed,
                  network,
                  utf8-string
import Network.Socket
import Data.ByteString.UTF8 as BSU

...

dbConn :: PGDatabase
dbConn =
    defaultPGDatabase
        { pgDBAddr = Right $ SockAddrInet 5432 (tupleToHostAddress (127, 1, 0, 2))
        , pgDBName = BSU.fromString "my_dbname"
        , pgDBUser = BSU.fromString "my_user"
          pgDBPass = BSU.fromString "my_pw"
        }

Ideally, I'd rely on env vars only but since I don't know how to deal with IO Strings yet, that's what I went with.

A little out of scope with this issue, but I'd like to implement a CRUD web app prototype with your library and possibly Servant

If you dont mind, could you recommend me a book or two to read to help with that ? (I prefer a practical rather than theoritical approach)

Thanks and have a great day

dylex commented 2 years ago

Made a few more minor changes. If you don't call useTPGDatabase it will use the TPG_ env vars, but this only works for compile-time, not run-time. It feels better to provide an example with the same config, but unfortunately because of template haskell limitations, you then have to put the config in a separate file.

You can avoid utf8-string by enabling OverloadedStrings and just using string literals. (You can also just use Data.ByteString.Char8.pack if you don't have any unicode.)

Example with System.Environment.getEnv:

dbConn :: IO PGDatabase
dbConn = do
  user <- getEnv "DB_USER"
  return defaultPGDatabase
    { pgDBUser = Data.ByteString.Char8.pack user
    }

-- in other file:
PG.useTPGDatabase =<< Language.Haskell.TH.runIO dbConn

main = do
  db <- pgConnect =<< dbConn

(adding necessary imports of course)

I haven't used servant and don't have any book recommendations, sorry. I mostly use things from the Yesod ecosystem.

benjamin-thomas commented 2 years ago

I haven't used servant and don't have any book recommendations, sorry.

That's alright, no worries :)

Thanks very much. I'm very happy I could get a taste of what your library can do.

The feedback feels nice, great job!