{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Logger (LoggingT, runLoggingT)
import Control.Monad.Trans.Resource
import Control.Monad.Reader
import qualified Data.ByteString.Char8 as B
import Data.List.NonEmpty (NonEmpty(..))
import Data.List (sort)
import Data.Text (Text)
import System.Log.FastLogger (fromLogStr)
import Test.Hspec
import Test.HUnit ((@?=))
import Database.Persist.Sql
import Database.Persist.Sql.Raw.QQ
import Database.Persist.Sqlite
import PersistTestPetType
import PersistentTestModels
main :: IO ()
main = hspec spec
_debugOn :: Bool
_debugOn = False
runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) a -> m a
runConn f = do
let printDebug = when _debugOn . B.putStrLn . fromLogStr
flip runLoggingT (\_ _ _ s -> printDebug s) $ do
withSqliteConn ":memory:" $ runSqlConn f
db :: SqlPersistT (LoggingT (ResourceT IO)) a -> IO a
db actions = do
runResourceT $ runConn $ do
_ <- runMigrationSilent testMigrate
actions <* transactionUndo
spec :: Spec
spec = describe "persistent-qq" $ do
it "sqlQQ/?-?" $ db $ do
ret <- [sqlQQ| SELECT #{2 :: Int}+#{2 :: Int} |]
liftIO $ ret @?= [Single (4::Int)]
it "sqlQQ/?-?" $ db $ do
ret <- [sqlQQ| SELECT #{5 :: Int}-#{3 :: Int} |]
liftIO $ ret @?= [Single (2::Int)]
it "sqlQQ/NULL" $ db $ do
ret <- [sqlQQ| SELECT NULL |]
liftIO $ ret @?= [Nothing :: Maybe (Single Int)]
it "sqlQQ/entity" $ db $ do
let insert'
:: PersistStore backend
=> PersistEntity val
=> PersistEntityBackend val ~ BaseBackend backend
=> MonadIO m
=> val
-> ReaderT backend m (Key val, val)
insert' v = insert v >>= \k -> return (k, v)
(p1k, p1) <- insert' $ Person "Mathias" 23 Nothing
(p2k, p2) <- insert' $ Person "Norbert" 44 Nothing
(p3k, _ ) <- insert' $ Person "Cassandra" 19 Nothing
(_ , _ ) <- insert' $ Person "Thiago" 19 Nothing
(a1k, a1) <- insert' $ Pet p1k "Rodolfo" Cat
(a2k, a2) <- insert' $ Pet p1k "Zeno" Cat
(a3k, a3) <- insert' $ Pet p2k "Lhama" Dog
(_ , _ ) <- insert' $ Pet p3k "Abacate" Cat
let runQuery
:: (RawSql a, Functor m, MonadIO m)
=> Int
-> ReaderT SqlBackend m [a]
runQuery age =
[sqlQQ|
SELECT ??, ??
FROM
^{Person},
^{Pet}
WHERE ^{Person}.@{PersonAge} >= #{age}
AND ^{Pet}.@{PetOwnerId} = ^{Person}.@{PersonId}
ORDER BY ^{Person}.@{PersonName}
|]
ret <- runQuery 20
liftIO $ ret @?= [ (Entity p1k p1, Entity a1k a1)
, (Entity p1k p1, Entity a2k a2)
, (Entity p2k p2, Entity a3k a3) ]
ret2 <- runQuery 20
liftIO $ ret2 @?= [ (Just (Entity p1k p1), Just (Entity a1k a1))
, (Just (Entity p1k p1), Just (Entity a2k a2))
, (Just (Entity p2k p2), Just (Entity a3k a3)) ]
ret3 <- runQuery 20
liftIO $ ret3 @?= [ Just (Entity p1k p1, Entity a1k a1)
, Just (Entity p1k p1, Entity a2k a2)
, Just (Entity p2k p2, Entity a3k a3) ]
it "sqlQQ/order-proof" $ db $ do
let p1 = Person "Zacarias" 93 Nothing
p1k <- insert p1
let runQuery
:: (RawSql a, Functor m, MonadIO m)
=> ReaderT SqlBackend m [a]
runQuery = [sqlQQ| SELECT ?? FROM ^{Person} |]
ret1 <- runQuery
ret2 <- runQuery :: (MonadIO m) => SqlPersistT m [Entity (ReverseFieldOrder Person)]
liftIO $ ret1 @?= [Entity p1k p1]
liftIO $ ret2 @?= [Entity (RFOKey $ unPersonKey p1k) (RFO p1)]
it "sqlQQ/OUTER JOIN" $ db $ do
let insert' :: (PersistStore backend, PersistEntity val, PersistEntityBackend val ~ BaseBackend backend, MonadIO m)
=> val -> ReaderT backend m (Key val, val)
insert' v = insert v >>= \k -> return (k, v)
(p1k, p1) <- insert' $ Person "Mathias" 23 Nothing
(p2k, p2) <- insert' $ Person "Norbert" 44 Nothing
(a1k, a1) <- insert' $ Pet p1k "Rodolfo" Cat
(a2k, a2) <- insert' $ Pet p1k "Zeno" Cat
ret <- [sqlQQ|
SELECT ??, ??
FROM ^{Person}
LEFT OUTER JOIN ^{Pet}
ON ^{Person}.@{PersonId} = ^{Pet}.@{PetOwnerId}
ORDER BY ^{Person}.@{PersonName}
|]
liftIO $ ret @?= [ (Entity p1k p1, Just (Entity a1k a1))
, (Entity p1k p1, Just (Entity a2k a2))
, (Entity p2k p2, Nothing) ]
it "sqlQQ/values syntax" $ db $ do
let insert' :: (PersistStore backend, PersistEntity val, PersistEntityBackend val ~ BaseBackend backend, MonadIO m)
=> val -> ReaderT backend m (Key val, val)
insert' v = insert v >>= \k -> return (k, v)
(p1k, p1) <- insert' $ Person "Mathias" 23 (Just "red")
(_ , _ ) <- insert' $ Person "Norbert" 44 (Just "green")
(p3k, p3) <- insert' $ Person "Cassandra" 19 (Just "blue")
(_ , _ ) <- insert' $ Person "Thiago" 19 (Just "yellow")
let
colors = Just "blue" :| Just "red" : [] :: NonEmpty (Maybe Text)
ret <- [sqlQQ|
SELECT ??
FROM ^{Person}
WHERE ^{Person}.@{PersonColor} IN %{colors}
|]
liftIO $ ret @?= [ (Entity p1k p1)
, (Entity p3k p3) ]
it "sqlQQ/rows syntax" $ do
let rows :: NonEmpty (Text, Int)
rows = ("Mathias", 23) :| ("Norbert", 44) : ("Cassandra", 19) : []
db $ do
[executeQQ|INSERT INTO ^{Person} (@{PersonName}, @{PersonAge}) VALUES *{rows}|]
map unSingle <$> [sqlQQ|SELECT @{PersonName} FROM ^{Person} ORDER BY @{PersonName}|]
`shouldReturn` sort ["Mathias", "Norbert", "Cassandra" :: Text]