@@ -11,15 +11,23 @@
13module Squeal.PostgreSQL.Transaction
13module Squeal.PostgreSQL.Transaction
14− ( transactionally
15+ transactionally
15 , transactionally_
16 , transactionally_
18 , rollback
19 , rollback
19 , onExceptionRollback
20 , onExceptionRollback
21+ -- * Transaction Mode
20 , TransactionMode (..)
22 , TransactionMode (..)
21 , IsolationLevel (..)
25 , IsolationLevel (..)
22− , ReadWriteMode (..)
26+ , renderIsolationLevel
29+ , DeferrableMode (..)
30+ , renderDeferrableMode
25import Control.Exception.Lifted
33import Control.Exception.Lifted
@@ -36,31 +44,39 @@ import Squeal.PostgreSQL.Prettyprint
36import Squeal.PostgreSQL.PQ
44import Squeal.PostgreSQL.PQ
37import Squeal.PostgreSQL.Schema
45import Squeal.PostgreSQL.Schema
47+-- | Run a query or manipulation `transactionally`.
39transactionally
48transactionally
40 :: (MonadBaseControl IO tx, MonadPQ schema tx)
49 :: (MonadBaseControl IO tx, MonadPQ schema tx)
41 => TransactionMode
50 => TransactionMode
51+ -> tx x -- ^ run inside a transaction
42− -> tx x -> tx x
52+ -> tx x
43transactionally mode tx = mask $ \restore -> do
53transactionally mode tx = mask $ \restore -> do
44 _ <- begin mode
54 _ <- begin mode
45 result <- restore tx `onException` rollback
55 result <- restore tx `onException` rollback
46 _ <- commit
56 _ <- commit
47 return result
57 return result
59+-- | Run a query or manipulation `transactionally_` in `defaultMode`.
49transactionally_
60transactionally_
50 :: (MonadBaseControl IO tx, MonadPQ schema tx)
61 :: (MonadBaseControl IO tx, MonadPQ schema tx)
62+ => tx x -- ^ run inside a transaction
51− => tx x -> tx x
63+ -> tx x
52−transactionally_ = transactionally (TransactionMode ReadCommitted ReadWrite)
64+transactionally_ = transactionally defaultMode
66+-- | @BEGIN@ a transaction.
54begin :: MonadPQ schema tx => TransactionMode -> tx (K Result NilRelation)
67begin :: MonadPQ schema tx => TransactionMode -> tx (K Result NilRelation)
55begin mode = manipulate . UnsafeManipulation $
68begin mode = manipulate . UnsafeManipulation $
56 "BEGIN" <+> renderTransactionMode mode <> ";"
69 "BEGIN" <+> renderTransactionMode mode <> ";"
71+-- | @COMMIT@ a transaction.
58commit :: MonadPQ schema tx => tx (K Result NilRelation)
72commit :: MonadPQ schema tx => tx (K Result NilRelation)
59commit = manipulate $ UnsafeManipulation "COMMIT;"
73commit = manipulate $ UnsafeManipulation "COMMIT;"
75+-- | @ROLLBACK@ a query or manipulation transaction.
61rollback :: MonadPQ schema tx => tx (K Result NilRelation)
76rollback :: MonadPQ schema tx => tx (K Result NilRelation)
62rollback = manipulate $ UnsafeManipulation "ROLLBACK;"
77rollback = manipulate $ UnsafeManipulation "ROLLBACK;"
79+-- | @ROLLBACK@ a transaction upon exception.
64onExceptionRollback
80onExceptionRollback
65 :: MonadBaseControl IO io
81 :: MonadBaseControl IO io
66 => PQ schema0 schema1 io x
82 => PQ schema0 schema1 io x
@@ -69,35 +85,131 @@ onExceptionRollback u = PQ $ \ conn -> mask $ \ restore ->
69 restore (runPQ u conn) `onException`
85 restore (runPQ u conn) `onException`
70 liftBase (LibPQ.exec (unK conn) "ROLLBACK")
86 liftBase (LibPQ.exec (unK conn) "ROLLBACK")
88+-- | The available transaction characteristics are the transaction `IsolationLevel`,
89+-- the transaction access mode (`ReadWrite` or `ReadOnly`), and the `DeferrableMode`.
72data TransactionMode = TransactionMode
90data TransactionMode = TransactionMode
73 { isolationLevel :: IsolationLevel
91 { isolationLevel :: IsolationLevel
74− , readWriteMode :: ReadWriteMode
92+ , accessMode :: AccessMode
93+ , deferrableMode :: DeferrableMode
75 } deriving (Show, Eq)
94 } deriving (Show, Eq)
96+-- | `TransactionMode` with a `Serializable` `IsolationLevel`,
97+-- `ReadWrite` `AccessMode` and `NotDeferrable` `DeferrableMode`.
98+defaultMode :: TransactionMode
99+defaultMode = TransactionMode Serializable ReadWrite NotDeferrable
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
107+-- | Render a `TransactionMode`.
77renderTransactionMode :: TransactionMode -> ByteString
108renderTransactionMode :: TransactionMode -> ByteString
78renderTransactionMode mode =
109renderTransactionMode mode =
79 "ISOLATION LEVEL"
110 "ISOLATION LEVEL"
80 <+> renderIsolationLevel (isolationLevel mode)
111 <+> renderIsolationLevel (isolationLevel mode)
81− <+> renderReadWriteMode (readWriteMode mode)
112+ <+> renderAccessMode (accessMode mode)
82−
113+ <+> renderDeferrableMode (deferrableMode mode)
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:
125+-- A transaction reads data written by a concurrent uncommitted transaction.
127+-- __Nonrepeatable read__
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).
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.
138+-- __Serialization anomaly__
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.
83data IsolationLevel
147data IsolationLevel
84 = Serializable
148 = 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.
85 | RepeatableRead
153 | RepeatableRead
154+ -- ^ Dirty read is not possible.
155+ -- Nonrepeatable read is not possible.
156+ -- Phantom read is not possible.
157+ -- Serialization anomaly is possible.
86 | ReadCommitted
158 | ReadCommitted
159+ -- ^ Dirty read is not possible.
160+ -- Nonrepeatable read is possible.
161+ -- Phantom read is possible.
162+ -- Serialization anomaly is possible.
164+ -- ^ Dirty read is not possible.
165+ -- Nonrepeatable read is possible.
166+ -- Phantom read is possible.
167+ -- Serialization anomaly is possible.
87 deriving (Show, Eq)
168 deriving (Show, Eq)
170+-- | Render an `IsolationLevel`.
89renderIsolationLevel :: IsolationLevel -> ByteString
171renderIsolationLevel :: IsolationLevel -> ByteString
90renderIsolationLevel = \case
172renderIsolationLevel = \case
91 Serializable -> "SERIALIZABLE"
173 Serializable -> "SERIALIZABLE"
92 ReadCommitted -> "READ COMMITTED"
174 ReadCommitted -> "READ COMMITTED"
175+ ReadUncommitted -> "READ UNCOMMITTED"
93 RepeatableRead -> "REPEATABLE READ"
176 RepeatableRead -> "REPEATABLE READ"
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.
96 = ReadWrite
188 = ReadWrite
97 | ReadOnly
189 | ReadOnly
98 deriving (Show, Eq)
190 deriving (Show, Eq)
100−renderReadWriteMode :: ReadWriteMode -> ByteString
192+-- | Render an `AccessMode`.
101−renderReadWriteMode = \case
193+renderAccessMode :: AccessMode -> ByteString
194+renderAccessMode = \case
102 ReadWrite -> "READ WRITE"
195 ReadWrite -> "READ WRITE"
103 ReadOnly -> "READ ONLY"
196 ReadOnly -> "READ ONLY"
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.
211+-- | Render a `DeferrableMode`.
212+renderDeferrableMode :: DeferrableMode -> ByteString
213+renderDeferrableMode = \case
214+ Deferrable -> "DEFERRABLE"
215+ NotDeferrable -> "NOT DEFERRABLE"