Performant PostgreSQL driver with a flexible mapping API
Go to file
Nikita Volkov 310cbd5901 Fix Travis
2015-08-10 08:16:43 +03:00
demo Rename "SEx" to "Ex", since we're not teenagers here 2014-12-29 12:20:44 +03:00
hspec Support for free variables by the quasi-quoter 2015-01-12 04:52:30 +03:00
hspec-postgres Multivalue clauses test 2015-01-09 02:48:37 +03:00
library GHC 7.10 support 2015-03-30 10:24:27 +03:00
.travis.yml Fix Travis 2015-08-10 08:16:43 +03:00
CHANGELOG.md Update the Attoparsec dep 2015-05-15 08:05:49 +03:00
hasql.cabal Relax dependencies 2015-08-10 08:15:07 +03:00
LICENSE Init 2014-08-03 17:48:18 +04:00
README.md Adds a basic README file 2015-06-02 13:11:58 -04:00

Hasql Build Status

Hasql provides a robust and concise yet powerful API for communication with arbitrary relational databases using SQL.

Currently the only backend available is for PostgreSQL (which can yield great performance improvements over HDBC or postgresql-simple).

The code used here file is the demo found in the repository

Openning a connection

For greater convenience the Hasql has a built-in connection pool. All interactions with the database backend are done within the context of such a pool.

So we have functions to create a pool and one to release all resources held by the pool:

H.acquirePool
  :: Hasql.Backend.Cx c =>
     Hasql.Backend.CxSettings c -> H.PoolSettings -> IO (H.Pool c)

and

H.releasePool :: H.Pool c -> IO ()

To create the pool we need to pass the connection settings (which are backend dependent) and the pool settings. The code sample below will open a connection to a PostgreSQL database.

{-# LANGUAGE QuasiQuotes, ScopedTypeVariables, OverloadedStrings #-}

-- Import the API from the "hasql" library
import qualified Hasql as H

-- Import the backend API from the "hasql-postgres" library
import qualified Hasql.Postgres as HP
let postgresSettings = HP.ParamSettings "localhost" 5432 "postgres" "" "postgres"

-- Prepare the pool settings with a smart constructor,
-- which checks the inputted values on correctness.
-- Set the connection pool size to 6 and the timeout to 30 seconds.
poolSettings <- maybe (fail "Improper session settings") return $ 
                  H.poolSettings 6 30

-- Acquire the database connections pool.
-- Gotta help the compiler with the type signature of the pool a bit.
pool :: H.Pool HP.Postgres 
       <- H.acquirePool postgresSettings poolSettings

Executing a statement

To execute statements we will use a Session, which is just a wrapper for the ReaderT monad. This allow us to use the pool for all our session sub-computations.

So the session function, is a wrapper for the runReaderT, and besides a parameter with the pool, we need to pass a function with the return type in H.Session monad. And as the return we get either a SessionError or the result of our function.

The function we will use to actually execute the transactions is the tx which conveniently enough receives a transaction mode, the transactions we want to execute (along with their session context) and returns the type H.Session c m r.

It is important to notice that running IO in Tx is prohibited.

let's take a look at the signatures proceding:

H.session
  :: H.Pool c -> H.Session c m a -> m (Either (H.SessionError c) a)

H.tx
  :: (Control.Monad.Trans.Control.MonadBaseControl IO m,
      Hasql.Backend.CxTx c) =>
     H.TxMode -> (forall s. H.Tx c s r) -> H.Session c m r

The following code excerpt shows us how the demo code uses these functions to open a session and start a transaction to create a new table:

-- Provide a context for execution of transactions.
-- 'Session' is merely a convenience wrapper around 'ReaderT'.
H.session pool $ do

  -- Execute a group of statements without any locking and ACID guarantees:
  H.tx Nothing $ do
    H.unitEx [H.stmt|DROP TABLE IF EXISTS a|]
    H.unitEx [H.stmt|CREATE TABLE a (id SERIAL NOT NULL, balance INT8, PRIMARY KEY (id))|]

Transactions (isolation levels and transaction modes)

You have probably noticed that the first parameter of tx belongs to the type TxMode. This parameter deserves some consideration, for it will determine the behaviour of our transaction. Let's take a look at its type definition:

type TxMode = Maybe (TxIsolationLevel, TxWriteMode)

data TxIsolationLevel =
  RepeatableReads |
  Serializable    |
  ReadCommitted   |
  ReadUncommitted

type TxWriteMode = Maybe Bool

So when the mode is Nothing, no transaction is explicitly estabilished on the server. In PostgreSQL's case this means all commands be commited immediatly after execution and their isolation level will be Read Committed.

If we pass the tuple, the first element will be the transaction isolation level, you can read more about transaction isolation levels on wikipedia.

The second element is the write mode, which will be interpreted as:

  • Nothing indicates a "read" mode.
  • Just True indicates a "write" mode.
  • Just False indicates a "write" mode without committing (can be useful for testing purposes).