on dev help sign in
docs
9a99370Eitan Chatav
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+15−16
@@ -13,10 +13,9 @@
13module Squeal.PostgreSQL.Migration
13module Squeal.PostgreSQL.Migration
14 ( -- * Migration
14 ( -- * Migration
15 Migration (..)
15 Migration (..)
16+ , Step (..)
16 , migrateUp
17 , migrateUp
17 , migrateDown
18 , migrateDown
18− -- * Step
19− , Step (..)
20 -- * MigrationTable
19 -- * MigrationTable
21 , MigrationTable
20 , MigrationTable
22 , createMigration
21 , createMigration
@@ -37,16 +36,7 @@ import Prelude hiding (id, (.))
37import Squeal.PostgreSQL
36import Squeal.PostgreSQL
38import Squeal.PostgreSQL.Transaction
37import Squeal.PostgreSQL.Transaction
39
38
40−-- | A `Step` of a `Migration`, should contain an inverse pair of
41−-- `up` and `down` instructions.
42−data Step io schema0 schema1 = Step
43− { name :: Text -- ^ The `name` of a `Step`.
44− -- Each `name` in a `Migration` should be unique.
45− , up :: PQ schema0 schema1 io () -- ^ The `up` instruction of a `Step`.
46− , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
47− }
48−
49−-- | A `Migration` is a chain of `Step`s
39+-- | A `Migration` is a schema-aligned sequence of `Step`s.
50data Migration io schema0 schema1 where
40data Migration io schema0 schema1 where
51 Done :: Migration io schema schema
41 Done :: Migration io schema schema
52 (:>>)
42 (:>>)
@@ -60,9 +50,18 @@ instance Category (Migration io) where
60 Done -> migration
50 Done -> migration
61 step :>> steps -> step :>> (steps >>> migration)
51 step :>> steps -> step :>> (steps >>> migration)
62
52
53+-- | A `Step` of a `Migration`, should contain an inverse pair of
54+-- `up` and `down` instructions and a unique `name`.
55+data Step io schema0 schema1 = Step
56+ { name :: Text -- ^ The `name` of a `Step`.
57+ -- Each `name` in a `Migration` should be unique.
58+ , up :: PQ schema0 schema1 io () -- ^ The `up` instruction of a `Step`.
59+ , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
60+ }
61+
63
62
64-- | Run a `Migration` by creating the `MigrationTable`
63-- | Run a `Migration` by creating the `MigrationTable`
65−-- if it does not exist and then for each each `Step`
64+-- if it does not exist and then in a transaction, for each each `Step`
66-- query to see if the `Step` is executed. If not, then
65-- query to see if the `Step` is executed. If not, then
67-- execute the `Step` and insert its row in `MigrationTable`.
66-- execute the `Step` and insert its row in `MigrationTable`.
68migrateUp
67migrateUp
@@ -74,7 +73,7 @@ migrateUp
74 io ()
73 io ()
75migrateUp migration =
74migrateUp migration =
76 define createMigration
75 define createMigration
77− & pqThen (begin (TransactionMode RepeatableRead ReadWrite))
76+ & pqThen (begin defaultMode)
78 & pqThen (upSteps migration)
77 & pqThen (upSteps migration)
79 & pqThen (void commit)
78 & pqThen (void commit)
80 where
79 where
@@ -118,7 +117,7 @@ migrateUp migration =
118 -- insert up step execution record
117 -- insert up step execution record
119
118
120-- | Rewind a `Migration` by creating the `MigrationTable`
119-- | Rewind a `Migration` by creating the `MigrationTable`
121−-- if it does not exist and then for each each `Step`
120+-- if it does not exist and then in a transaction, for each each `Step`
122-- query to see if the `Step` is executed. If it is, then
121-- query to see if the `Step` is executed. If it is, then
123-- rewind the `Step` and delete its row in `MigrationTable`.
122-- rewind the `Step` and delete its row in `MigrationTable`.
124migrateDown
123migrateDown
@@ -130,7 +129,7 @@ migrateDown
130 io ()
129 io ()
131migrateDown migration =
130migrateDown migration =
132 define createMigration
131 define createMigration
133− & pqThen (begin (TransactionMode RepeatableRead ReadWrite) & void)
132+ & pqThen (begin defaultMode)
134 & pqThen (downSteps migration)
133 & pqThen (downSteps migration)
135 & pqThen (void commit)
134 & pqThen (void commit)
136 where
135 where
squeal-postgresql/src/Squeal/PostgreSQL/Transaction.hs+123−11
@@ -11,15 +11,23 @@
11#-}
11#-}
12
12
13module Squeal.PostgreSQL.Transaction
13module Squeal.PostgreSQL.Transaction
14+ ( -- * Transactions
14− ( transactionally
15+ transactionally
15 , transactionally_
16 , transactionally_
16 , begin
17 , begin
17 , commit
18 , commit
18 , rollback
19 , rollback
19 , onExceptionRollback
20 , onExceptionRollback
21+ -- * Transaction Mode
20 , TransactionMode (..)
22 , TransactionMode (..)
23+ , defaultMode
24+ , longRunningMode
21 , IsolationLevel (..)
25 , IsolationLevel (..)
22− , ReadWriteMode (..)
26+ , renderIsolationLevel
27+ , AccessMode (..)
28+ , renderAccessMode
29+ , DeferrableMode (..)
30+ , renderDeferrableMode
23 ) where
31 ) where
24
32
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
38
46
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
48
58
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
53
65
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 <> ";"
57
70
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;"
60
74
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;"
63
78
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")
71
87
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)
76
95
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`.
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)
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.
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.
163+ | ReadUncommitted
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)
88
169
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"
94
177
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
96 = ReadWrite
188 = ReadWrite
97 | ReadOnly
189 | ReadOnly
98 deriving (Show, Eq)
190 deriving (Show, Eq)
99
191
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"
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"