@@ -1,7 +1,10 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE ScopedTypeVariables #-}
2import Common
3import Common
3import Database.PostgreSQL.Simple.FromField (FromField)
4import Database.PostgreSQL.Simple.FromField (FromField)
4import Database.PostgreSQL.Simple.HStore
5import Database.PostgreSQL.Simple.HStore
6+import qualified Database.PostgreSQL.Simple.Transaction as ST
7+import Control.Applicative
5import Control.Exception as E
8import Control.Exception as E
6import Control.Monad
9import Control.Monad
7import Data.ByteString (ByteString)
10import Data.ByteString (ByteString)
@@ -27,6 +30,7 @@ tests =
27 , TestLabel "Time" . testTime
30 , TestLabel "Time" . testTime
28 , TestLabel "Array" . testArray
31 , TestLabel "Array" . testArray
29 , TestLabel "HStore" . testHStore
32 , TestLabel "HStore" . testHStore
33+ , TestLabel "Savepoint" . testSavepoint
32testBytea :: TestEnv -> Test
36testBytea :: TestEnv -> Test
@@ -118,6 +122,79 @@ testHStore TestEnv{..} = TestCase $ do
118 m' <- query conn "SELECT ?::hstore" m
122 m' <- query conn "SELECT ?::hstore" m
119 [m] @?= m'
123 [m] @?= m'
125+testSavepoint :: TestEnv -> Test
126+testSavepoint TestEnv{..} = TestCase $ do
127+ True <- expectError ST.isNoActiveTransactionError $
128+ withSavepoint conn $ return ()
130+ let getRows :: IO [Int]
131+ getRows = map fromOnly <$> query_ conn "SELECT a FROM tmp_savepoint ORDER BY a"
132+ withTransaction conn $ do
133+ execute_ conn "CREATE TEMPORARY TABLE tmp_savepoint (a INT UNIQUE)"
134+ execute_ conn "INSERT INTO tmp_savepoint VALUES (1)"
137+ withSavepoint conn $ do
138+ execute_ conn "INSERT INTO tmp_savepoint VALUES (2)"
143+ withSavepoint conn $ do
144+ execute_ conn "INSERT INTO tmp_savepoint VALUES (3)"
146+ True <- expectError isUniqueViolation $
147+ execute_ conn "INSERT INTO tmp_savepoint VALUES (2)"
148+ True <- expectError ST.isFailedTransactionError getRows
150+ -- Body returning successfully after handling error,
151+ -- but 'withSavepoint' will roll back without complaining.
153+ -- Rolling back clears the error condition.
156+ -- 'withSavepoint' will roll back after an exception, even if the
157+ -- exception wasn't SQL-related.
158+ True <- expectError (== TestException) $
159+ withSavepoint conn $ do
160+ execute_ conn "INSERT INTO tmp_savepoint VALUES (3)"
162+ throwIO TestException
165+ -- Nested savepoint can be rolled back while the
166+ -- outer effects are retained.
167+ withSavepoint conn $ do
168+ execute_ conn "INSERT INTO tmp_savepoint VALUES (3)"
169+ True <- expectError isUniqueViolation $
170+ withSavepoint conn $ do
171+ execute_ conn "INSERT INTO tmp_savepoint VALUES (4)"
172+ [1,2,3,4] <- getRows
173+ execute_ conn "INSERT INTO tmp_savepoint VALUES (4)"
180+ -- Transaction committed successfully, even though there were errors
181+ -- (but we rolled them back).
188+ (io >> return False) `E.catch` \ex ->
189+ if p ex then return True else throwIO ex
190+ isUniqueViolation SqlError{..} = sqlState == "23505"
194+ deriving (Eq, Show, Typeable)
196+instance Exception TestException
121------------------------------------------------------------------------
198------------------------------------------------------------------------
123-- | Action for connecting to the database that will be used for testing.
200-- | Action for connecting to the database that will be used for testing.