192 lines
6.3 KiB
Haskell
192 lines
6.3 KiB
Haskell
{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings, GADTs, FlexibleContexts, EmptyDataDecls, GeneralizedNewtypeDeriving, Rank2Types, ConstraintKinds, MultiParamTypeClasses #-}
|
|
|
|
module Main (main) where
|
|
|
|
import Control.Applicative (Applicative(..))
|
|
import Control.Monad.Base (MonadBase(..))
|
|
import Control.Monad.IO.Class (MonadIO(liftIO))
|
|
import Control.Monad.Logger (MonadLogger(..), LogLevel(..))
|
|
import Control.Monad.Trans.Control (MonadBaseControl(..))
|
|
import Database.Esqueleto
|
|
import Database.Persist.Sqlite (runSqlConn, withSqliteConn)
|
|
import Database.Persist.TH
|
|
import Language.Haskell.TH (Loc(..))
|
|
import System.IO (stderr)
|
|
import Test.Hspec
|
|
import Test.Hspec.Expectations
|
|
|
|
import qualified Control.Monad.Trans.Reader as R
|
|
import qualified Data.Conduit as C
|
|
import qualified Data.Text as T
|
|
import qualified System.Log.FastLogger as FL
|
|
|
|
|
|
-- Test schema
|
|
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|
|
|
Person
|
|
name String
|
|
age Int Maybe
|
|
deriving Eq Show
|
|
BlogPost
|
|
title String
|
|
authorId PersonId
|
|
deriving Eq Show
|
|
Follow
|
|
follower PersonId
|
|
followed PersonId
|
|
deriving Eq Show
|
|
|]
|
|
|
|
|
|
main :: IO ()
|
|
main = do
|
|
let p1 = Person "John" (Just 36)
|
|
p2 = Person "Rachel" Nothing
|
|
p3 = Person "Mike" (Just 17)
|
|
hspec $ do
|
|
describe "select" $ do
|
|
it "works for a single value" $
|
|
run $ do
|
|
ret <- select $ return $ val (3 :: Int)
|
|
liftIO $ ret `shouldBe` [ Single 3 ]
|
|
describe "select/from" $ do
|
|
it "works for a simple example" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
ret <- select $
|
|
from $ \person ->
|
|
return person
|
|
liftIO $ ret `shouldBe` [ Entity p1k p1 ]
|
|
it "works for a simple self-join (one entity)" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
ret <- select $
|
|
from $ \(person1, person2) ->
|
|
return (person1, person2)
|
|
liftIO $ ret `shouldBe` [ (Entity p1k p1, Entity p1k p1) ]
|
|
it "works for a simple self-join (two entities)" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
p2k <- insert p2
|
|
ret <- select $
|
|
from $ \(person1, person2) ->
|
|
return (person1, person2)
|
|
liftIO $ ret `shouldBe` [ (Entity p1k p1, Entity p1k p1)
|
|
, (Entity p1k p1, Entity p2k p2)
|
|
, (Entity p2k p2, Entity p1k p1)
|
|
, (Entity p2k p2, Entity p2k p2) ]
|
|
it "works for a simple projection" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
p2k <- insert p2
|
|
ret <- select $
|
|
from $ \p ->
|
|
return (p ^. PersonId, p ^. PersonName)
|
|
liftIO $ ret `shouldBe` [ (Single p1k, Single (personName p1))
|
|
, (Single p2k, Single (personName p2)) ]
|
|
it "works for a simple projection with a simple self-join" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
p2k <- insert p2
|
|
ret <- select $
|
|
from $ \(p1, p2) ->
|
|
return (p1 ^. PersonName, p2 ^. PersonName)
|
|
liftIO $ ret `shouldBe` [ (Single (personName p1), Single (personName p1))
|
|
, (Single (personName p1), Single (personName p2))
|
|
, (Single (personName p2), Single (personName p1))
|
|
, (Single (personName p2), Single (personName p2)) ]
|
|
describe "select/where_" $ do
|
|
it "works for a simple example with (==.)" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
_ <- insert p2
|
|
_ <- insert p3
|
|
ret <- select $
|
|
from $ \p -> do
|
|
where_ (p ^. PersonName ==. val "John")
|
|
return p
|
|
liftIO $ ret `shouldBe` [ Entity p1k p1 ]
|
|
it "works for a simple example with (>.)" $
|
|
run $ do
|
|
p1k <- insert p1
|
|
_ <- insert p2
|
|
_ <- insert p3
|
|
ret <- select $
|
|
from $ \p -> do
|
|
where_ (p ^. PersonAge >. val (Just 17))
|
|
return p
|
|
liftIO $ ret `shouldBe` [ Entity p1k p1 ]
|
|
it "works for a simple example with (>.) and not_" $
|
|
run $ do
|
|
_ <- insert p1
|
|
_ <- insert p2
|
|
p3k <- insert p3
|
|
ret <- select $
|
|
from $ \p -> do
|
|
where_ (not_ $ p ^. PersonAge >. val (Just 17))
|
|
return p
|
|
liftIO $ ret `shouldBe` [ Entity p3k p3 ]
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
|
type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m
|
|
, C.MonadUnsafeIO m, C.MonadThrow m )
|
|
|
|
|
|
run, runSilent, runVerbose :: (forall m. RunDbMonad m => SqlPersist (C.ResourceT m) a) -> IO a
|
|
runSilent act = run_worker act
|
|
runVerbose act = execVerbose $ run_worker act
|
|
run =
|
|
runSilent
|
|
-- runVerbose
|
|
|
|
|
|
run_worker :: RunDbMonad m => SqlPersist (C.ResourceT m) a -> m a
|
|
run_worker =
|
|
C.runResourceT .
|
|
withSqliteConn ":memory:" .
|
|
runSqlConn .
|
|
(runMigrationSilent migrateAll >>)
|
|
|
|
|
|
newtype Verbose a = Verbose { unVerbose :: R.ReaderT FL.Logger IO a }
|
|
deriving (Functor, Applicative, Monad, MonadIO, C.MonadUnsafeIO, C.MonadThrow)
|
|
|
|
instance MonadBase IO Verbose where
|
|
liftBase = Verbose . liftBase
|
|
|
|
instance MonadBaseControl IO Verbose where
|
|
newtype StM Verbose a = StMV { unStMV :: StM (R.ReaderT FL.Logger IO) a }
|
|
liftBaseWith f = Verbose . liftBaseWith $ \r -> f (fmap StMV . r . unVerbose)
|
|
restoreM = Verbose . restoreM . unStMV
|
|
|
|
instance MonadLogger Verbose where
|
|
monadLoggerLog loc level msg =
|
|
Verbose $ do
|
|
logger <- R.ask
|
|
liftIO $ FL.loggerPutStr logger $
|
|
[ FL.LB "["
|
|
, FL.LS $ case level of
|
|
LevelOther t -> T.unpack t
|
|
_ -> drop 5 $ show level
|
|
, FL.LB "] "
|
|
, FL.toLogStr msg
|
|
, FL.LB " @("
|
|
, FL.LS $ (loc_package loc) ++
|
|
':' : (loc_module loc) ++
|
|
' ' : (loc_filename loc) ++
|
|
':' : (show . fst $ loc_start loc) ++
|
|
':' : (show . snd $ loc_start loc)
|
|
, FL.LB ")\n"
|
|
]
|
|
|
|
|
|
execVerbose :: Verbose a -> IO a
|
|
execVerbose (Verbose act) = do
|
|
logger <- FL.mkLogger True stderr
|
|
x <- R.runReaderT act logger
|
|
FL.loggerFlush logger
|
|
return x
|