| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Hasql.Mapping.IsTransaction
Description
An explicit export list is required here (unlike this package's other modules) purely
mechanically: a module with no export list only exports entities it defines, not ones it
merely imports, so IsolationLevel and Mode would otherwise stay invisible to any instance
that imports only this module.
Synopsis
- class IsTransaction a where
- type Result a
- isolation :: IsolationLevel
- mode :: Mode
- transaction :: a -> Transaction (Result a)
- data IsolationLevel
- data Mode
- toSessionWithUnboundedRetries :: IsTransaction a => a -> Session (Result a)
- toSessionWithoutRetries :: IsTransaction a => a -> Session (Result a)
Documentation
class IsTransaction a where #
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:
memptyisminBound(ReadCommittedandRead), so that a component with no opinion never downgrades a component that has one.- An omitted class method defaults to
SerializableandWrite, 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
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 albumIdMinimal complete definition
Methods
Defaults to Serializable, the conservative choice.
Defaults to Write, the conservative choice.
transaction :: a -> Transaction (Result a) #
data IsolationLevel #
For reference see the Postgres' documentation.
Constructors
| ReadCommitted | |
| RepeatableRead | |
| Serializable |
Instances
Instances
| Monoid Mode # | |
| Semigroup Mode # | Combines two modes by picking the one that grants more capability.
|
| Eq Mode # | |
| Ord Mode # | |
| Bounded Mode # | |
| Enum Mode # | |
| Show Mode # | |
toSessionWithUnboundedRetries :: IsTransaction a => a -> Session (Result a) #
Runs the transaction with its declared isolation and mode, retrying it indefinitely on
serialization failures and deadlocks.
hasql-transaction's retry is a fix loop with no backoff and no cap: a Serializable
transaction under sustained contention can spin indefinitely, holding a connection and never
surfacing an error. Prefer toSessionWithoutRetries with your own bounded retry loop unless
that is an acceptable risk for the operation.
toSessionWithoutRetries :: IsTransaction a => a -> Session (Result a) #