I am a Java programmer who learns Haskell.
I work on a small web-app that uses Happstack and talks to a database via HDBC.
I've written select and exec functions and I use them like this:
module Main where
import Control.Exception (throw)
import Database.HDBC
import Database.HDBC.Sqlite3 -- just for this example, I use MySQL in production
main = do
exec "CREATE TABLE IF NOT EXISTS users (name VARCHAR(80) NOT NULL)" []
exec "INSERT INTO users VALUES ('John')" []
exec "INSERT INTO users VALUES ('Rick')" []
rows <- select "SELECT name FROM users" []
let toS x = (fromSql x)::String
let names = map (toS . head) rows
print names
Very simple as you see. There is query, params and result.
Connection creation and commit/rollback stuff is hidden inside select and exec.
This is good, I don't want to care about it in my "logic" code.
exec :: String -> [SqlValue] -> IO Integer
exec query params = withDb $ \c -> run c query params
select :: String -> [SqlValue] -> IO [[SqlValue]]
select query params = withDb $ \c -> quickQuery' c query params
withDb :: (Connection -> IO a) -> IO a
withDb f = do
conn <- handleSqlError $ connectSqlite3 "users.db"
catchSql
(do r <- f conn
commit conn
disconnect conn
return r)
(\e@(SqlError _ _ m) -> do
rollback conn
disconnect conn
throw e)
Bad points:
- a new connection is always created for every call - this kills performance on heavy load
- DB url "users.db" is hardcoded - I can't reuse these functions across other projects w/o editing
QUESTION 1: how to introduce a pool of connections with some defined (min, max) number of concurrent connections, so the connections will be reused between select/exec calls?
QUESTION 2: How to make "users.db" string configurable? (How to move it to client code?)
It should be a transparent feature: user code should not require explicit connection handling/release.
IO
monad, so maybeReaderT IO
? Sounds reasonable. – Audubon