squeal-postgresql-0.4.0.0: test/Specs/ExceptionHandling.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module ExceptionHandling
( specs
, User (..)
)
where
import Control.Monad (void)
import Control.Monad.Base (MonadBase)
import qualified Data.ByteString.Char8 as Char8
import Data.Int (Int16)
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL
import Squeal.PostgreSQL.Migration
import Test.Hspec
type Schema =
'[ "users" ::: 'Table (
'[ "pk_users" ::: 'PrimaryKey '["id"]
, "unique_names" ::: 'Unique '["name"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
, "vec" ::: 'NoDef :=> 'NotNull ('PGvararray ('Null 'PGint2))
])
, "emails" ::: 'Table (
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext
])
]
data User =
User { userName :: Text
, userEmail :: Maybe Text
, userVec :: Vector (Maybe Int16) }
deriving (Show, GHC.Generic)
instance SOP.Generic User
instance SOP.HasDatatypeInfo User
insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
'[ "fromOnly" ::: 'NotNull 'PGint4 ]
insertUser = insertRows #users
(Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
OnConflictDoRaise (Returning (#id `as` #fromOnly))
setup :: Definition '[] Schema
setup =
createTable #users
( serial `as` #id :*
(text & notNullable) `as` #name :*
(vararray int2 & notNullable) `as` #vec )
( primaryKey #id `as` #pk_users
:* unique #name `as` #unique_names )
>>>
createTable #emails
( serial `as` #id :*
(int & notNullable) `as` #user_id :*
(text & nullable) `as` #email )
( primaryKey #id `as` #pk_emails :*
foreignKey #user_id #users #id
OnDeleteCascade OnUpdateCascade `as` #fk_user_id )
teardown :: Definition Schema '[]
teardown = dropTable #emails >>> dropTable #users
migration :: Migration IO '[] Schema
migration = Migration { name = "test"
, up = void $ define setup
, down = void $ define teardown }
setupDB :: IO ()
setupDB = void . withConnection connectionString $
migrateUp $ single migration
dropDB :: IO ()
dropDB = void . withConnection connectionString $
migrateDown $ single migration
connectionString :: Char8.ByteString
connectionString = "host=localhost port=5432 dbname=exampledb"
testUser :: User
testUser = User "TestUser" Nothing []
newUser :: (MonadBase IO m, MonadPQ Schema m) => User -> m ()
newUser u = void $ manipulateParams insertUser (userName u, userVec u)
insertUserTwice :: (MonadBase IO m, MonadPQ Schema m) => m ()
insertUserTwice = newUser testUser >> newUser testUser
specs :: SpecWith ()
specs = before_ setupDB $ after_ dropDB $
describe "Exceptions" $ do
let
dupKeyErr = PQException FatalError (Just "23505")
(Just "ERROR: duplicate key value violates unique constraint \"unique_names\"\nDETAIL: Key (name)=(TestUser) already exists.\n")
it "should be thrown for unique constraint violation in a manipulation" $
withConnection connectionString insertUserTwice
`shouldThrow` (== dupKeyErr)
it "should be rethrown for unique constraint violation in a manipulation by a transaction" $
withConnection connectionString (transactionally_ insertUserTwice)
`shouldThrow` (== dupKeyErr)