hasql-mapping
Safe HaskellNone
LanguageHaskell2010

Hasql.Mapping

Description

Reexports of classes without methods.

To access the methods import the class-specific modules preferably qualified.

Synopsis

Documentation

class IsScalar a #

Mapping to a scalar value. Anything but array.

The current Hasql API doesn't provide a typesafe boundary to enforce the value being scalar, so consider this to be a part of the contract to not define instances for array mappings using this class.

Minimal complete definition

encoder, decoder

Instances

Instances details
IsScalar Value #

Maps to PostgreSQL jsonb.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar ByteString #

Maps to PostgreSQL bytea.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Int16 #

Maps to PostgreSQL int2.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Int32 #

Maps to PostgreSQL int4.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Int64 #

Maps to PostgreSQL int8.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar IPRange #

Maps to PostgreSQL inet.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Scientific #

Maps to PostgreSQL numeric.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Text #

Maps to PostgreSQL text.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Day #

Maps to PostgreSQL date.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar DiffTime #

Maps to PostgreSQL interval.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar UTCTime #

Maps to PostgreSQL timestamptz.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar LocalTime #

Maps to PostgreSQL timestamp.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar TimeOfDay #

Maps to PostgreSQL time.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar UUID #

Maps to PostgreSQL uuid.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Bool #

Maps to PostgreSQL bool.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Double #

Maps to PostgreSQL float8.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Float #

Maps to PostgreSQL float4.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar Int #

Maps to PostgreSQL int8.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar (TimeOfDay, TimeZone) #

Maps to PostgreSQL timetz.

Instance details

Defined in Hasql.Mapping.IsScalar

IsScalar (Word8, Word8, Word8, Word8, Word8, Word8) #

Maps to PostgreSQL macaddr.

Instance details

Defined in Hasql.Mapping.IsScalar

class IsStatement a #

Evidence that a data-structure models statement parameters determining the statement and its result type.

Supports a modularisation pattern, where you define everything related to one statement in an isolated module. This pattern leads to high code cohesion and low coupling.

Example of such a module

Expand
module MusicCatalogueDb.Statements.SelectArtistIdsByName where

import Data.Functor.Contravariant
import Data.Text (Text)
import Data.UUID (UUID)
import Data.Vector (Vector)
import qualified Hasql.Decoders as Decoders
import qualified Hasql.Encoders as Encoders
import Hasql.Mapping.IsStatement
import Prelude

data SelectArtistIdsByName = SelectArtistIdsByName
  { name :: Text
  }

type SelectArtistIdsByNameResult = Vector SelectArtistIdsByNameResultRow

data SelectArtistIdsByNameResultRow = SelectArtistIdsByNameResultRow
  { id :: UUID
  }

instance IsStatement SelectArtistIdsByName where
  type Result SelectArtistIdsByName = SelectArtistIdsByNameResult
  statement =
    Statement.preparable sql encoder decoder
    where
      sql =
        "select id from artist\n\
        \where name = $1\n\
        \limit 1"
      encoder =
        mconcat
          [ (\(SelectArtistIdsByName x) -> x)
              >$< Encoders.param (Encoders.nonNullable Encoders.text)
          ]
      decoder =
        Decoders.rowVector
          ( SelectArtistIdsByNameResultRow
              <$> Decoders.column (Decoders.nonNullable Decoders.uuid)
          )

Minimal complete definition

statement

class IsTransaction a #

Evidence that a data-structure determines an atomic, retryable database transaction.

isolation and mode are properties of the transaction, not of the call site: whether an operation needs Serializable is a fact about what it does, and a caller reaching for toSessionWithUnboundedRetries or toSessionWithoutRetries cannot override or forget them.

The defaults are the conservative ones (Serializable and Write), so the safe case is free and every relaxation is explicit and reviewable in the instance. The opposite defaults would make an under-isolated transaction invisible.

A composite transaction declares the join of its components by hand, using IsolationLevel and Mode's Semigroup instances:

instance IsTransaction Composite where
  isolation = isolation \@Part1 <> isolation \@Part2
  mode = mode \@Part1 <> mode \@Part2

The two identities are deliberately opposite, because a reader who learns one will guess the other wrong:

  • mempty is minBound (ReadCommitted and Read), so that a component with no opinion never downgrades a component that has one.
  • An omitted class method defaults to Serializable and Write, so that an author who never considered the question gets the safe answer.

Both are conservative, by opposite rules. The join is declared explicitly rather than derived, so changing which components make up a composite requires updating these declarations as well.

Example of such a module

Expand
module MusicCatalogueDb.Transactions.InsertAlbumWithTracks where

import Hasql.Mapping.IsTransaction
import qualified Hasql.Transaction as Transaction
import qualified MusicCatalogueDb.Statements.InsertAlbum as InsertAlbum
import qualified MusicCatalogueDb.Statements.InsertTrack as InsertTrack
import Prelude

data InsertAlbumWithTracks = InsertAlbumWithTracks
  { album :: InsertAlbum.InsertAlbum,
    tracks :: [InsertTrack.InsertTrack]
  }

type InsertAlbumWithTracksResult = InsertAlbum.InsertAlbumResult

instance IsTransaction InsertAlbumWithTracks where
  type Result InsertAlbumWithTracks = InsertAlbumWithTracksResult
  isolation = ReadCommitted -- inserts only fresh rows, so no anomaly exposure
  transaction params = do
    albumId <- Transaction.statement params.album InsertAlbum.statement
    for_ params.tracks \track ->
      Transaction.statement track InsertTrack.statement
    pure albumId

Minimal complete definition

transaction

class IsSession a #

Evidence that a data-structure determines a top-level database operation: one with its own error channel and IO capability, the two things a bare Transaction cannot have.

A single Result rather than separate success/error associated types. An operation with a domain failure expresses it as type XResult = Either XError A, which names the outcome once at the definition rather than in every signature. Separate error/success types would additionally force infallible sessions — bulk loads via onLibpqConnection, LISTEN/NOTIFY, batching through pipeline — to write Error X = Void and their callers to match an impossible Left.

Unlike IsTransaction, this class needs no runner: session already produces a Session.

Example: insert-and-catch

Expand

Insert-and-catch is preferable to check-then-insert: the latter needs Serializable to be correct and costs an extra round trip, while the former is correct at any isolation level in one. Catching above toSessionWithoutRetries is safe because by the time an error escapes the runner, the transaction has already been rolled back:

module MusicCatalogueDb.Sessions.RegisterAlbum where

import Hasql.Mapping.IsSession
import qualified Hasql.Mapping.IsTransaction as IsTransaction
import Prelude

data RegisterAlbum = RegisterAlbum { ... }

data RegisterAlbumError = AlbumAlreadyExists

type RegisterAlbumResult = Either RegisterAlbumError AlbumId

instance IsSession RegisterAlbum where
  type Result RegisterAlbum = RegisterAlbumResult
  session params =
    catchingSqlState (\case "23505" -> Just AlbumAlreadyExists; _ -> Nothing)
      $ IsTransaction.toSessionWithoutRetries (InsertAlbumWithTracks params.album params.tracks)

(catchingSqlState is proposed upstream at nikita-volkov/hasql#322 and is not yet part of hasql; the shape above is illustrative of how it will compose over this class.)

Minimal complete definition

session