@@ -11,15 +11,23 @@
1111#-}
1212
1313module Squeal.PostgreSQL.Transaction
14− ( transactionally
14+ ( -- * Transactions
15+ transactionally
1516 , transactionally_
1617 , begin
1718 , commit
1819 , rollback
1920 , onExceptionRollback
21+ -- * Transaction Mode
2022 , TransactionMode (..)
23+ , defaultMode
24+ , longRunningMode
2125 , IsolationLevel (..)
22− , ReadWriteMode (..)
26+ , renderIsolationLevel
27+ , AccessMode (..)
28+ , renderAccessMode
29+ , DeferrableMode (..)
30+ , renderDeferrableMode
2331 ) where
2432
2533import Control.Exception.Lifted
@@ -36,31 +44,39 @@ import Squeal.PostgreSQL.Prettyprint
3644import Squeal.PostgreSQL.PQ
3745import Squeal.PostgreSQL.Schema
3846
47+-- | Run a query or manipulation `transactionally`.
3948transactionally
4049 :: (MonadBaseControl IO tx, MonadPQ schema tx)
4150 => TransactionMode
42− -> tx x -> tx x
51+ -> tx x -- ^ run inside a transaction
52+ -> tx x
4353transactionally mode tx = mask $ \restore -> do
4454 _ <- begin mode
4555 result <- restore tx `onException` rollback
4656 _ <- commit
4757 return result
4858
59+-- | Run a query or manipulation `transactionally_` in `defaultMode`.
4960transactionally_
5061 :: (MonadBaseControl IO tx, MonadPQ schema tx)
51− => tx x -> tx x
52−transactionally_ = transactionally (TransactionMode ReadCommitted ReadWrite)
62+ => tx x -- ^ run inside a transaction
63+ -> tx x
64+transactionally_ = transactionally defaultMode
5365
66+-- | @BEGIN@ a transaction.
5467begin :: MonadPQ schema tx => TransactionMode -> tx (K Result NilRelation)
5568begin mode = manipulate . UnsafeManipulation $
5669 "BEGIN" <+> renderTransactionMode mode <> ";"
5770
71+-- | @COMMIT@ a transaction.
5872commit :: MonadPQ schema tx => tx (K Result NilRelation)
5973commit = manipulate $ UnsafeManipulation "COMMIT;"
6074
75+-- | @ROLLBACK@ a query or manipulation transaction.
6176rollback :: MonadPQ schema tx => tx (K Result NilRelation)
6277rollback = manipulate $ UnsafeManipulation "ROLLBACK;"
6378
79+-- | @ROLLBACK@ a transaction upon exception.
6480onExceptionRollback
6581 :: MonadBaseControl IO io
6682 => PQ schema0 schema1 io x
@@ -69,35 +85,131 @@ onExceptionRollback u = PQ $ \ conn -> mask $ \ restore ->
6985 restore (runPQ u conn) `onException`
7086 liftBase (LibPQ.exec (unK conn) "ROLLBACK")
7187
88+-- | The available transaction characteristics are the transaction `IsolationLevel`,
89+-- the transaction access mode (`ReadWrite` or `ReadOnly`), and the `DeferrableMode`.
7290data TransactionMode = TransactionMode
7391 { isolationLevel :: IsolationLevel
74− , readWriteMode :: ReadWriteMode
92+ , accessMode :: AccessMode
93+ , deferrableMode :: DeferrableMode
7594 } deriving (Show, Eq)
7695
96+-- | `TransactionMode` with a `Serializable` `IsolationLevel`,
97+-- `ReadWrite` `AccessMode` and `NotDeferrable` `DeferrableMode`.
98+defaultMode :: TransactionMode
99+defaultMode = TransactionMode Serializable ReadWrite NotDeferrable
100+
101+-- | `TransactionMode` with a `Serializable` `IsolationLevel`,
102+-- `ReadOnly` `AccessMode` and `Deferrable` `DeferrableMode`.
103+-- This mode is well suited for long-running reports or backups.
104+longRunningMode :: TransactionMode
105+longRunningMode = TransactionMode Serializable ReadOnly Deferrable
106+
107+-- | Render a `TransactionMode`.
77108renderTransactionMode :: TransactionMode -> ByteString
78109renderTransactionMode mode =
79110 "ISOLATION LEVEL"
80111 <+> renderIsolationLevel (isolationLevel mode)
81− <+> renderReadWriteMode (readWriteMode mode)
82−
112+ <+> renderAccessMode (accessMode mode)
113+ <+> renderDeferrableMode (deferrableMode mode)
114+
115+-- | The SQL standard defines four levels of transaction isolation.
116+-- The most strict is `Serializable`, which is defined by the standard in a paragraph
117+-- which says that any concurrent execution of a set of `Serializable` transactions is
118+-- guaranteed to produce the same effect as running them one at a time in some order.
119+-- The other three levels are defined in terms of phenomena, resulting from interaction
120+-- between concurrent transactions, which must not occur at each level.
121+-- The phenomena which are prohibited at various levels are:
122+--
123+-- __Dirty read__
124+--
125+-- A transaction reads data written by a concurrent uncommitted transaction.
126+--
127+-- __Nonrepeatable read__
128+--
129+-- A transaction re-reads data it has previously read and finds that data
130+-- has been modified by another transaction (that committed since the initial read).
131+--
132+-- __Phantom read__
133+--
134+-- A transaction re-executes a query returning a set of rows that satisfy
135+-- a search condition and finds that the set of rows satisfying the condition
136+-- has changed due to another recently-committed transaction.
137+--
138+-- __Serialization anomaly__
139+--
140+-- The result of successfully committing a group of transactions is inconsistent
141+-- with all possible orderings of running those transactions one at a time.
142+-- In PostgreSQL, you can request any of the four standard transaction
143+-- isolation levels, but internally only three distinct isolation levels are implemented,
144+-- i.e. PostgreSQL's `ReadUncommitted` mode behaves like `ReadCommitted`.
145+-- This is because it is the only sensible way to map the standard isolation levels to
146+-- PostgreSQL's multiversion concurrency control architecture.
83147data IsolationLevel
84148 = Serializable
149+ -- ^ Dirty read is not possible.
150+ -- Nonrepeatable read is not possible.
151+ -- Phantom read is not possible.
152+ -- Serialization anomaly is not possible.
85153 | RepeatableRead
154+ -- ^ Dirty read is not possible.
155+ -- Nonrepeatable read is not possible.
156+ -- Phantom read is not possible.
157+ -- Serialization anomaly is possible.
86158 | ReadCommitted
159+ -- ^ Dirty read is not possible.
160+ -- Nonrepeatable read is possible.
161+ -- Phantom read is possible.
162+ -- Serialization anomaly is possible.
163+ | ReadUncommitted
164+ -- ^ Dirty read is not possible.
165+ -- Nonrepeatable read is possible.
166+ -- Phantom read is possible.
167+ -- Serialization anomaly is possible.
87168 deriving (Show, Eq)
88169
170+-- | Render an `IsolationLevel`.
89171renderIsolationLevel :: IsolationLevel -> ByteString
90172renderIsolationLevel = \case
91173 Serializable -> "SERIALIZABLE"
92174 ReadCommitted -> "READ COMMITTED"
175+ ReadUncommitted -> "READ UNCOMMITTED"
93176 RepeatableRead -> "REPEATABLE READ"
94177
95−data ReadWriteMode
178+-- | The transaction access mode determines whether the transaction is `ReadWrite` or `ReadOnly`.
179+-- `ReadWrite` is the default. When a transaction is `ReadOnly`,
180+-- the following SQL commands are disallowed:
181+-- @INSERT@, @UPDATE@, @DELETE@, and @COPY FROM@
182+-- if the table they would write to is not a temporary table;
183+-- all @CREATE@, @ALTER@, and @DROP@ commands;
184+-- @COMMENT@, @GRANT@, @REVOKE@, @TRUNCATE@;
185+-- and @EXPLAIN ANALYZE@ and @EXECUTE@ if the command they would execute is among those listed.
186+-- This is a high-level notion of `ReadOnly` that does not prevent all writes to disk.
187+data AccessMode
96188 = ReadWrite
97189 | ReadOnly
98190 deriving (Show, Eq)
99191
100−renderReadWriteMode :: ReadWriteMode -> ByteString
101−renderReadWriteMode = \case
192+-- | Render an `AccessMode`.
193+renderAccessMode :: AccessMode -> ByteString
194+renderAccessMode = \case
102195 ReadWrite -> "READ WRITE"
103196 ReadOnly -> "READ ONLY"
197+
198+-- | The `Deferrable` transaction property has no effect
199+-- unless the transaction is also `Serializable` and `ReadOnly`.
200+-- When all three of these properties are selected for a transaction,
201+-- the transaction may block when first acquiring its snapshot,
202+-- after which it is able to run without the normal overhead of a
203+-- `Serializable` transaction and without any risk of contributing
204+-- to or being canceled by a serialization failure.
205+-- This mode is well suited for long-running reports or backups.
206+data DeferrableMode
207+ = Deferrable
208+ | NotDeferrable
209+ deriving (Show, Eq)
210+
211+-- | Render a `DeferrableMode`.
212+renderDeferrableMode :: DeferrableMode -> ByteString
213+renderDeferrableMode = \case
214+ Deferrable -> "DEFERRABLE"
215+ NotDeferrable -> "NOT DEFERRABLE"