2019-07-16 20:15:56 +03:00
|
|
|
{-# LANGUAGE
|
|
|
|
DataKinds
|
|
|
|
, DeriveAnyClass
|
|
|
|
, DeriveGeneric
|
|
|
|
, DerivingStrategies
|
|
|
|
, DuplicateRecordFields
|
|
|
|
, FlexibleContexts
|
|
|
|
, OverloadedLabels
|
|
|
|
, OverloadedLists
|
|
|
|
, OverloadedStrings
|
|
|
|
, TypeApplications
|
|
|
|
, TypeFamilies
|
|
|
|
, TypeInType
|
|
|
|
, TypeOperators
|
|
|
|
#-}
|
|
|
|
|
2019-08-28 21:29:37 +03:00
|
|
|
module Main (main) where
|
2019-07-16 20:15:56 +03:00
|
|
|
|
|
|
|
import Control.Concurrent.Async (replicateConcurrently)
|
|
|
|
import Data.ByteString (ByteString)
|
2019-07-22 20:01:25 +03:00
|
|
|
import Data.Int (Int32)
|
2019-07-16 20:15:56 +03:00
|
|
|
import Data.Text (Text)
|
|
|
|
import Test.Hspec
|
|
|
|
|
|
|
|
import qualified Generics.SOP as SOP
|
|
|
|
import qualified GHC.Generics as GHC
|
|
|
|
|
|
|
|
import Squeal.PostgreSQL
|
|
|
|
|
2019-08-28 21:29:37 +03:00
|
|
|
main :: IO ()
|
|
|
|
main = hspec spec
|
|
|
|
|
2019-07-16 20:15:56 +03:00
|
|
|
type Schemas = Public
|
|
|
|
'[ "users" ::: 'Table (
|
|
|
|
'[ "pk_users" ::: 'PrimaryKey '["id"]
|
|
|
|
, "unique_names" ::: 'Unique '["name"]
|
|
|
|
] :=>
|
|
|
|
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
|
|
|
|
, "name" ::: 'NoDef :=> 'NotNull 'PGtext ] ) ]
|
|
|
|
|
|
|
|
data User = User
|
|
|
|
{ userName :: Text
|
|
|
|
} deriving stock (Eq, Show, GHC.Generic)
|
|
|
|
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
|
|
|
|
|
|
|
|
insertUser :: Manipulation_ Schemas User ()
|
|
|
|
insertUser = insertInto_ #users
|
|
|
|
(Values_ (Default `as` #id :* Set (param @1) `as` #name))
|
|
|
|
|
|
|
|
setup :: Definition (Public '[]) Schemas
|
|
|
|
setup =
|
|
|
|
createTable #users
|
|
|
|
( serial `as` #id :*
|
|
|
|
notNullable text `as` #name )
|
|
|
|
( primaryKey #id `as` #pk_users :*
|
|
|
|
unique #name `as` #unique_names )
|
|
|
|
|
|
|
|
teardown :: Definition Schemas (Public '[])
|
|
|
|
teardown = dropTable #users
|
|
|
|
|
|
|
|
silence :: MonadPQ schemas pq => pq ()
|
|
|
|
silence = manipulate_ $
|
|
|
|
UnsafeManipulation "SET client_min_messages TO WARNING;"
|
|
|
|
|
|
|
|
setupDB :: IO ()
|
|
|
|
setupDB = withConnection connectionString $
|
|
|
|
silence & pqThen (define setup)
|
|
|
|
|
|
|
|
dropDB :: IO ()
|
|
|
|
dropDB = withConnection connectionString $
|
|
|
|
silence & pqThen (define teardown)
|
|
|
|
|
|
|
|
connectionString :: ByteString
|
|
|
|
connectionString = "host=localhost port=5432 dbname=exampledb"
|
|
|
|
|
|
|
|
spec :: Spec
|
|
|
|
spec = before_ setupDB . after_ dropDB $ do
|
|
|
|
|
|
|
|
describe "Exceptions" $ do
|
|
|
|
|
|
|
|
let
|
|
|
|
testUser = User "TestUser"
|
|
|
|
newUser = manipulateParams_ insertUser
|
|
|
|
insertUserTwice = newUser testUser >> newUser testUser
|
|
|
|
err23505 = PQException $ PQState 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 constraint violation" $
|
|
|
|
withConnection connectionString insertUserTwice
|
|
|
|
`shouldThrow` (== err23505)
|
|
|
|
|
|
|
|
it "should be rethrown for constraint violation in a transaction" $
|
|
|
|
withConnection connectionString (transactionally_ insertUserTwice)
|
|
|
|
`shouldThrow` (== err23505)
|
|
|
|
|
2019-07-22 20:01:25 +03:00
|
|
|
describe "Pools" $
|
2019-07-16 20:15:56 +03:00
|
|
|
|
|
|
|
it "should manage concurrent transactions" $ do
|
|
|
|
pool <- createConnectionPool
|
|
|
|
"host=localhost port=5432 dbname=exampledb" 1 0.5 10
|
|
|
|
let
|
|
|
|
query :: Query_ (Public '[]) () (Only Char)
|
|
|
|
query = values_ (literal 'a' `as` #fromOnly)
|
|
|
|
session = usingConnectionPool pool . transactionally_ $ do
|
|
|
|
result <- runQuery query
|
|
|
|
Just (Only chr) <- firstRow result
|
|
|
|
return chr
|
|
|
|
chrs <- replicateConcurrently 10 session
|
2019-07-22 20:01:25 +03:00
|
|
|
chrs `shouldSatisfy` all (== 'a')
|
|
|
|
|
|
|
|
describe "Ranges" $
|
|
|
|
|
|
|
|
it "should correctly decode ranges" $ do
|
|
|
|
|
|
|
|
rangesOut <- withConnection connectionString $ do
|
|
|
|
let
|
|
|
|
query :: Query_ (Public '[]) () (Only (Range Int32))
|
|
|
|
query = values
|
|
|
|
( range int4range (atLeast 3) `as` #fromOnly )
|
|
|
|
[ range int4range (3 <=..< 5) `as` #fromOnly
|
|
|
|
, range int4range Empty `as` #fromOnly
|
|
|
|
, range int4range whole `as` #fromOnly ]
|
|
|
|
getRows =<< runQuery query
|
|
|
|
(fromOnly <$> rangesOut :: [Range Int32]) `shouldBe`
|
|
|
|
[ atLeast 3, 3 <=..< 5, Empty, whole ]
|
2019-10-23 20:44:06 +03:00
|
|
|
|
|
|
|
describe "Parameters" $ do
|
|
|
|
|
|
|
|
it "should run queries that don't reference all their parameters" $ do
|
|
|
|
|
|
|
|
out <- withConnection connectionString $ do
|
|
|
|
let
|
|
|
|
query :: Query_ (Public '[]) (Char,Int32) (Only Int32)
|
|
|
|
query = values_ (param @2 `as` #fromOnly)
|
|
|
|
firstRow =<< runQueryParams query ('a', 3 :: Int32)
|
|
|
|
(fromOnly <$> out :: Maybe Int32) `shouldBe` Just 3
|