Hasql Documentation

repository·master·Indexed 20 days ago

https://github.com/nikita-volkov/hasql

A high-performance, type-safe PostgreSQL driver for Haskell. Designed as a modular ecosystem, it includes the core hasql library and specialized extensions such as hasql-transaction for STM-inspired transactions, hasql-pool for connection pooling, and hasql-th for compile-time SQL syntax checking. It supports pluggable transports via the pqi library, offering both a stable C-backed FFI transport and a pure-Haskell native implementation.

Tokens
2K
Snippets
3
Records
4
Agent score
19%

What's inside Hasql

  1. Overview of the Hasql ecosystem

    master

    Hasql is a granular ecosystem of composable libraries designed to be simple and focused. Instead of a single monolithic library, you can pick and choose specific extensions based on your needs:

    • hasql: The core library providing essential abstractions over PostgreSQL client functionality and value mapping.
    • hasql-transaction: Composable STM-inspired database transactions with automated conflict resolution.
    • hasql-pool: Specialized connection pooling for Hasql.
    • hasql-postgresql-types: Precise modeling of PostgreSQL types.
    • hasql-dynamic-statements: Toolkit for generating statements based on parameters.
    • hasql-th: Template Haskell utilities for compile-time syntax checking and automatic statement declaration.
    • hasql-cursor-query: Declarative abstraction over cursors (recommended).
    • hasql-cursor-transaction: Lower-level cursor abstraction allowing simultaneous fetching from multiple cursors.
    • hasql-migration: Port of postgresql-simple-migration.
    • hasql-listen-notify / hasql-notifications: Support for asynchronous notifications.
    • hasql-optparse-applicative: Parsers for optparse-applicative.
    • hasql-implicits: Provides default codecs for standard types.
    • hasql-interpolate: QuasiQuoter for interpolating Haskell expressions into queries.
  2. How to acquire a connection with pluggable transports

    master

    Hasql uses the pqi library to provide pluggable PostgreSQL transports. You can choose between the stable, C-backed Pqi.Ffi transport or the alpha, pure-Haskell Pqi.Native transport. Swapping between them is a one-line change in the Hasql.Connection.acquire call.

    • Pqi.Ffi: The stable, production-proven default (requires libpq).
    • Pqi.Native: An alpha, pure-Haskell implementation of the Postgres wire protocol. It is fully interchangeable with Pqi.Ffi but not yet proven at production scale.
    import Pqi.Ffi qualified    -- the existing C-backed libpq transport
    import Pqi.Native qualified -- alpha: pure-Haskell, no C dependency
    
    -- Using the stable FFI transport
    connection <- Hasql.Connection.acquire Pqi.Ffi.adapter settings
    
    -- Using the alpha Native transport
    connection <- Hasql.Connection.acquire Pqi.Native.adapter settings
  3. Use hasql-th for compile-time checked statements

    master

    For most use cases, it is recommended to use the hasql-th library. It provides a QuasiQuoter that validates your SQL at compile-time and automatically generates the necessary encoders and decoders, reducing boilerplate and errors.

    import qualified Hasql.TH as TH
    import qualified Hasql.Statement as Statement
    import Data.Int
    
    sumStatement :: Statement.Statement (Int64, Int64) Int64
    sumStatement =
      [TH.singletonStatement| 
        select ($1 :: int8 + $2 :: int8) :: int8
      |]
    
    divModStatement :: Statement.Statement (Int64, Int64) (Int64, Int64)
    divModStatement =
      [TH.singletonStatement| 
        select
          (($1 :: int8) / ($2 :: int8)) :: int8,
          (($1 :: int8) % ($2 :: int8)) :: int8
      |]
  4. Implement a basic Hasql application

    master

    A complete Hasql application involves defining Statements (SQL, encoders, and decoders), composing them into a Session, and executing that session using a Connection.

    To disable prepared statements (for example, when using pgbouncer), use Settings.noPreparedStatements True in your connection settings.

    {-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
    
    import Data.Functor.Contravariant
    import Data.Int
    import Hasql.Session (Session)
    import Prelude
    import qualified Hasql.Connection as Connection
    import qualified Hasql.Connection.Settings as Settings
    import qualified Hasql.Decoders as Decoders
    import qualified Hasql.Encoders as Encoders
    import qualified Hasql.Session as Session
    import qualified Hasql.Statement as Statement
    import qualified Pqi.Ffi
    
    main :: IO ()
    main = do
      Right connection <- Connection.acquire Pqi.Ffi.adapter connectionSettings
      result <- Connection.use connection (sumAndDivModSession 3 8 3)
      print result
      where
        connectionSettings =
          mconcat
            [ Settings.hostAndPort "localhost" 5432,
              Settings.user "postgres",
              Settings.password "postgres",
              Settings.dbname "postgres"
            ]
    
    sumAndDivModSession :: Int64 -> Int64 -> Int64 -> Session (Int64, Int64)
    sumAndDivModSession a b c = do
      sumOfAAndB <- Session.statement (a, b) sumStatement
      Session.statement (sumOfAAndB, c) divModStatement
    
    sumStatement :: Statement.Statement (Int64, Int64) Int64
    sumStatement = Statement.preparable sql encoder decoder
      where
        sql = "select $1 + $2"
        encoder =
          mconcat
            [
              fst >$< Encoders.param (Encoders.nonNullable Encoders.int8),
              snd >$< Encoders.param (Encoders.nonNullable Encoders.int8)
            ]
        decoder =
          Decoders.singleRow
            (Decoders.column (Decoders.nonNullable Decoders.int8))
    
    divModStatement :: Statement.Statement (Int64, Int64) (Int64, Int64)
    divModStatement = Statement.preparable sql encoder decoder
      where
        sql = "select $1 / $2, $1 % $2"
        encoder =
          mconcat
            [ fst >$< Encoders.param (Encoders.nonNullable Encoders.int8),
              snd >$< Encoders.param (Encoders.nonNullable Encoders.int8)
            ]
        decoder =
          Decoders.singleRow
            ( (,) 
                <$> Decoders.column (Decoders.nonNullable Decoders.int8)
                <*> Decoders.column (Decoders.nonNullable Decoders.int8)
            )