* rewriting tests * tests now run in 1.45 seconds * tests pass * fix json * fix tests * add helper for setting the database state * clean things up a bit
224 lines
6.4 KiB
Haskell
224 lines
6.4 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
module MySQL.Test where
|
|
|
|
import Common.Test.Import hiding (from, on)
|
|
|
|
import Control.Applicative
|
|
import Control.Monad (void)
|
|
import Control.Monad.IO.Class (MonadIO(liftIO))
|
|
import Control.Monad.Logger (runNoLoggingT, runStderrLoggingT)
|
|
import Control.Monad.Trans.Reader (ReaderT, mapReaderT)
|
|
import qualified Control.Monad.Trans.Resource as R
|
|
import Database.Esqueleto
|
|
import Database.Esqueleto.Experimental hiding (from, on)
|
|
import qualified Database.Esqueleto.Experimental as Experimental
|
|
import Database.Persist.MySQL
|
|
( connectDatabase
|
|
, connectHost
|
|
, connectPassword
|
|
, connectPort
|
|
, connectUser
|
|
, defaultConnectInfo
|
|
, withMySQLConn
|
|
, createMySQLPool
|
|
)
|
|
|
|
import Test.Hspec
|
|
|
|
import Common.Test
|
|
|
|
testMysqlSum :: SpecDb
|
|
testMysqlSum = do
|
|
itDb "works with sum_" $ do
|
|
_ <- insert' p1
|
|
_ <- insert' p2
|
|
_ <- insert' p3
|
|
_ <- insert' p4
|
|
ret <- select $
|
|
from $ \p->
|
|
return $ joinV $ sum_ (p ^. PersonAge)
|
|
liftIO $ ret `shouldBe` [ Value $ Just (36 + 17 + 17 :: Double ) ]
|
|
|
|
testMysqlTwoAscFields :: SpecDb
|
|
testMysqlTwoAscFields = do
|
|
itDb "works with two ASC fields (one call)" $ do
|
|
p1e <- insert' p1
|
|
p2e <- insert' p2
|
|
p3e <- insert' p3
|
|
p4e <- insert' p4
|
|
ret <- select $
|
|
from $ \p -> do
|
|
orderBy [asc (p ^. PersonAge), asc (p ^. PersonName)]
|
|
return p
|
|
liftIO $ ret `shouldBe` [ p2e, p4e, p3e, p1e ]
|
|
|
|
testMysqlOneAscOneDesc :: SpecDb
|
|
testMysqlOneAscOneDesc = do
|
|
itDb "works with one ASC and one DESC field (two calls)" $ do
|
|
p1e <- insert' p1
|
|
p2e <- insert' p2
|
|
p3e <- insert' p3
|
|
p4e <- insert' p4
|
|
ret <- select $
|
|
from $ \p -> do
|
|
orderBy [desc (p ^. PersonAge)]
|
|
orderBy [asc (p ^. PersonName)]
|
|
return p
|
|
liftIO $ ret `shouldBe` [ p1e, p4e, p3e, p2e ]
|
|
|
|
|
|
|
|
|
|
testMysqlCoalesce :: SpecDb
|
|
testMysqlCoalesce = do
|
|
itDb "works on PostgreSQL and MySQL with <2 arguments" $ do
|
|
_ :: [Value (Maybe Int)] <-
|
|
select $
|
|
from $ \p -> do
|
|
return (coalesce [p ^. PersonAge])
|
|
return ()
|
|
|
|
|
|
|
|
|
|
testMysqlUpdate :: SpecDb
|
|
testMysqlUpdate = do
|
|
itDb "works on a simple example" $ do
|
|
p1k <- insert p1
|
|
p2k <- insert p2
|
|
p3k <- insert p3
|
|
let anon = "Anonymous"
|
|
() <- update $ \p -> do
|
|
set p [ PersonName =. val anon
|
|
, PersonAge *=. just (val 2) ]
|
|
where_ (p ^. PersonName !=. val "Mike")
|
|
n <- updateCount $ \p -> do
|
|
set p [ PersonAge +=. just (val 1) ]
|
|
where_ (p ^. PersonName !=. val "Mike")
|
|
ret <- select $
|
|
from $ \p -> do
|
|
orderBy [ asc (p ^. PersonName), asc (p ^. PersonAge) ]
|
|
return p
|
|
-- MySQL: nulls appear first, and update returns actual number
|
|
-- of changed rows
|
|
liftIO $ n `shouldBe` 1
|
|
liftIO $ ret `shouldBe` [ Entity p2k (Person anon Nothing (Just 37) 2)
|
|
, Entity p1k (Person anon (Just 73) Nothing 1)
|
|
, Entity p3k p3 ]
|
|
|
|
nameContains :: (SqlString s)
|
|
=> (SqlExpr (Value [Char])
|
|
-> SqlExpr (Value s)
|
|
-> SqlExpr (Value Bool))
|
|
-> s
|
|
-> [Entity Person]
|
|
-> SqlPersistT IO ()
|
|
nameContains f t expected = do
|
|
ret <- select $
|
|
from $ \p -> do
|
|
where_ (f
|
|
(p ^. PersonName)
|
|
(concat_ [(%), val t, (%)]))
|
|
orderBy [asc (p ^. PersonName)]
|
|
return p
|
|
liftIO $ ret `shouldBe` expected
|
|
|
|
|
|
testMysqlTextFunctions :: SpecDb
|
|
testMysqlTextFunctions = do
|
|
describe "text functions" $ do
|
|
itDb "like, (%) and (++.) work on a simple example" $ do
|
|
[p1e, p2e, p3e, p4e] <- mapM insert' [p1, p2, p3, p4]
|
|
nameContains like "h" [p1e, p2e]
|
|
nameContains like "i" [p4e, p3e]
|
|
nameContains like "iv" [p4e]
|
|
|
|
|
|
testMysqlUnionWithLimits :: SpecDb
|
|
testMysqlUnionWithLimits = do
|
|
describe "MySQL Union" $ do
|
|
itDb "supports limit/orderBy by parenthesizing" $ do
|
|
mapM_ (insert . Foo) [1..6]
|
|
|
|
let q1 = do
|
|
foo <- Experimental.from $ Table @Foo
|
|
where_ $ foo ^. FooName <=. val 3
|
|
orderBy [asc $ foo ^. FooName]
|
|
limit 2
|
|
pure $ foo ^. FooName
|
|
|
|
let q2 = do
|
|
foo <- Experimental.from $ Table @Foo
|
|
where_ $ foo ^. FooName >. val 3
|
|
orderBy [asc $ foo ^. FooName]
|
|
limit 2
|
|
pure $ foo ^. FooName
|
|
|
|
|
|
ret <- select $ Experimental.from $ q1 `union_` q2
|
|
liftIO $ ret `shouldMatchList` [Value 1, Value 2, Value 4, Value 5]
|
|
|
|
spec :: Spec
|
|
spec = beforeAll mkConnectionPool $ do
|
|
tests
|
|
|
|
describe "MySQL specific tests" $ do
|
|
-- definitely doesn't work at the moment
|
|
-- testMysqlRandom
|
|
testMysqlSum
|
|
testMysqlTwoAscFields
|
|
testMysqlOneAscOneDesc
|
|
testMysqlCoalesce
|
|
testMysqlUpdate
|
|
testMysqlTextFunctions
|
|
testMysqlUnionWithLimits
|
|
|
|
verbose :: Bool
|
|
verbose = False
|
|
|
|
migrateIt :: R.MonadUnliftIO m => SqlPersistT m ()
|
|
migrateIt = do
|
|
mapReaderT R.runResourceT $ void $ runMigrationSilent migrateAll
|
|
cleanDB
|
|
|
|
mkConnectionPool :: IO ConnectionPool
|
|
mkConnectionPool = do
|
|
ci <- isCI
|
|
let connInfo
|
|
| ci =
|
|
defaultConnectInfo
|
|
{ connectHost = "127.0.0.1"
|
|
, connectUser = "travis"
|
|
, connectPassword = "esqutest"
|
|
, connectDatabase = "esqutest"
|
|
, connectPort = 33306
|
|
}
|
|
| otherwise =
|
|
defaultConnectInfo
|
|
{ connectHost = "localhost"
|
|
, connectUser = "travis"
|
|
, connectPassword = "esqutest"
|
|
, connectDatabase = "esqutest"
|
|
, connectPort = 3306
|
|
}
|
|
pool <-
|
|
if verbose
|
|
then
|
|
runStderrLoggingT $
|
|
createMySQLPool connInfo 4
|
|
else
|
|
runNoLoggingT $
|
|
createMySQLPool connInfo 4
|
|
|
|
|
|
flip runSqlPool pool $ do
|
|
migrateIt
|
|
cleanDB
|
|
|
|
pure pool
|