Merge pull request #30 from jcristovao/upstream

PostgreSQL and MySQL support
This commit is contained in:
Felipe Lessa 2013-09-15 09:05:35 -07:00
commit 59deede08a
2 changed files with 109 additions and 7 deletions

View File

@ -47,6 +47,14 @@ source-repository head
type: git type: git
location: git://github.com/meteficha/esqueleto.git location: git://github.com/meteficha/esqueleto.git
Flag postgresql
Description: test postgresql. default is to test sqlite.
Default: False
Flag mysql
Description: test MySQL/MariaDB. default is to test sqlite.
Default: False
library library
exposed-modules: exposed-modules:
Database.Esqueleto Database.Esqueleto
@ -90,3 +98,20 @@ test-suite test
-- This library -- This library
, esqueleto , esqueleto
if flag(postgresql)
build-depends:
postgresql-simple >= 0.2
, postgresql-libpq >= 0.6
, persistent-postgresql >= 1.2.0
cpp-options: -DWITH_POSTGRESQL
if flag(mysql)
build-depends:
mysql-simple >= 0.2.2.3
, mysql >= 0.1.1.3
, persistent-mysql >= 1.2.0
cpp-options: -DWITH_MYSQL

View File

@ -10,6 +10,7 @@
, TemplateHaskell , TemplateHaskell
, TypeFamilies , TypeFamilies
, ScopedTypeVariables , ScopedTypeVariables
, CPP
#-} #-}
module Main (main) where module Main (main) where
@ -20,11 +21,22 @@ import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT, runNoLoggingT)
import Control.Monad.Trans.Control (MonadBaseControl(..)) import Control.Monad.Trans.Control (MonadBaseControl(..))
import Database.Esqueleto import Database.Esqueleto
import Database.Persist.Sqlite (withSqliteConn) import Database.Persist.Sqlite (withSqliteConn)
#if defined (WITH_POSTGRESQL)
import Database.Persist.Postgresql (withPostgresqlConn)
#elif defined (WITH_MYSQL)
import Database.Persist.MySQL ( withMySQLConn
, connectHost
, connectDatabase
, connectUser
, connectPassword
, defaultConnectInfo)
#endif
import Database.Persist.TH import Database.Persist.TH
import Test.Hspec import Test.Hspec
import qualified Data.Conduit as C import qualified Data.Conduit as C
import qualified Data.Set as S import qualified Data.Set as S
import qualified Data.List as L
-- Test schema -- Test schema
@ -43,6 +55,10 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase|
deriving Eq Show deriving Eq Show
|] |]
-- | this could be achieved with S.fromList, but not all lists
-- have Ord instances
sameElementsAs :: Eq a => [a] -> [a] -> Bool
sameElementsAs l1 l2 = null (l1 L.\\ l2)
main :: IO () main :: IO ()
main = do main = do
@ -96,10 +112,10 @@ main = do
ret <- select $ ret <- select $
from $ \(person1, person2) -> from $ \(person1, person2) ->
return (person1, person2) return (person1, person2)
liftIO $ ret `shouldBe` [ (p1e, p1e) liftIO $ ret `shouldSatisfy` sameElementsAs [ (p1e, p1e)
, (p1e, p2e) , (p1e, p2e)
, (p2e, p1e) , (p2e, p1e)
, (p2e, p2e) ] , (p2e, p2e) ]
it "works for a self-join via sub_select" $ it "works for a self-join via sub_select" $
run $ do run $ do
@ -149,7 +165,8 @@ main = do
ret <- select $ ret <- select $
from $ \(pa, pb) -> from $ \(pa, pb) ->
return (pa ^. PersonName, pb ^. PersonName) return (pa ^. PersonName, pb ^. PersonName)
liftIO $ ret `shouldBe` [ (Value (personName p1), Value (personName p1)) liftIO $ ret `shouldSatisfy` sameElementsAs
[ (Value (personName p1), Value (personName p1))
, (Value (personName p1), Value (personName p2)) , (Value (personName p1), Value (personName p2))
, (Value (personName p2), Value (personName p1)) , (Value (personName p2), Value (personName p1))
, (Value (personName p2), Value (personName p2)) ] , (Value (personName p2), Value (personName p2)) ]
@ -343,7 +360,11 @@ main = do
it "works with random_" $ it "works with random_" $
run $ do run $ do
#if defined(WITH_POSTGRESQL) || defined(WITH_MYSQL)
ret <- select $ return (random_ :: SqlExpr (Value Double))
#else
ret <- select $ return (random_ :: SqlExpr (Value Int)) ret <- select $ return (random_ :: SqlExpr (Value Int))
#endif
return () return ()
it "works with round_" $ it "works with round_" $
@ -462,7 +483,13 @@ main = do
from $ \p -> do from $ \p -> do
orderBy [asc (p ^. PersonAge), asc (p ^. PersonName)] orderBy [asc (p ^. PersonAge), asc (p ^. PersonName)]
return p return p
-- in PostgreSQL nulls are bigger than everything
#ifdef WITH_POSTGRESQL
liftIO $ ret `shouldBe` [ p4e, p3e, p1e , p2e ]
#else
-- in SQLite and MySQL, its the reverse
liftIO $ ret `shouldBe` [ p2e, p4e, p3e, p1e ] liftIO $ ret `shouldBe` [ p2e, p4e, p3e, p1e ]
#endif
it "works with one ASC and one DESC field" $ it "works with one ASC and one DESC field" $
run $ do run $ do
@ -474,7 +501,11 @@ main = do
from $ \p -> do from $ \p -> do
orderBy [desc (p ^. PersonAge), asc (p ^. PersonName)] orderBy [desc (p ^. PersonAge), asc (p ^. PersonName)]
return p return p
#ifdef WITH_POSTGRESQL
liftIO $ ret `shouldBe` [ p2e, p1e, p4e, p3e ]
#else
liftIO $ ret `shouldBe` [ p1e, p4e, p3e, p2e ] liftIO $ ret `shouldBe` [ p1e, p4e, p3e, p2e ]
#endif
it "works with a sub_select" $ it "works with a sub_select" $
run $ do run $ do
@ -578,10 +609,27 @@ main = do
from $ \p -> do from $ \p -> do
orderBy [ asc (p ^. PersonName), asc (p ^. PersonAge) ] orderBy [ asc (p ^. PersonName), asc (p ^. PersonAge) ]
return p return p
-- PostgreSQL: nulls are bigger than data, and update returns
-- matched rows, not actually changed rows.
#if defined(WITH_POSTGRESQL)
liftIO $ n `shouldBe` 2
liftIO $ ret `shouldBe` [ Entity p1k (Person anon (Just 73))
, Entity p2k (Person anon Nothing)
, Entity p3k p3 ]
-- MySQL: nulls appear first, and update returns actual number
-- of changed rows
#elif defined(WITH_MYSQL)
liftIO $ n `shouldBe` 1
liftIO $ ret `shouldBe` [ Entity p2k (Person anon Nothing)
, Entity p1k (Person anon (Just 73))
, Entity p3k p3 ]
#else
-- SQLite: nulls appear first, update returns matched rows.
liftIO $ n `shouldBe` 2 liftIO $ n `shouldBe` 2
liftIO $ ret `shouldBe` [ Entity p2k (Person anon Nothing) liftIO $ ret `shouldBe` [ Entity p2k (Person anon Nothing)
, Entity p1k (Person anon (Just 73)) , Entity p1k (Person anon (Just 73))
, Entity p3k p3 ] , Entity p3k p3 ]
#endif
it "works with a subexpression having COUNT(*)" $ it "works with a subexpression having COUNT(*)" $
run $ do run $ do
@ -755,6 +803,20 @@ insert' v = flip Entity v <$> insert v
type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m
, C.MonadUnsafeIO m, C.MonadThrow m ) , C.MonadUnsafeIO m, C.MonadThrow m )
#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)
-- With SQLite and in-memory databases, a separate connection implies a
-- separate database. With 'actual databases', the data is persistent and
-- thus must be cleaned after each test.
-- TODO: there is certainly a better way...
cleanDB
:: (forall m. RunDbMonad m
=> SqlPersistT (C.ResourceT m) ())
cleanDB = do
delete $ from $ \(blogpost :: SqlExpr (Entity BlogPost))-> return ()
delete $ from $ \(follow :: SqlExpr (Entity Follow)) -> return ()
delete $ from $ \(person :: SqlExpr (Entity Person)) -> return ()
#endif
run, runSilent, runVerbose :: (forall m. RunDbMonad m => SqlPersistT (C.ResourceT m) a) -> IO a run, runSilent, runVerbose :: (forall m. RunDbMonad m => SqlPersistT (C.ResourceT m) a) -> IO a
runSilent act = runNoLoggingT $ run_worker act runSilent act = runNoLoggingT $ run_worker act
@ -770,8 +832,23 @@ verbose = True
run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a
run_worker = run_worker act =
C.runResourceT . C.runResourceT .
#if defined(WITH_POSTGRESQL)
withPostgresqlConn "host=localhost port=5432 user=joao dbname=esqueleto" .
#elif defined (WITH_MYSQL)
withMySQLConn defaultConnectInfo
{ connectHost = "localhost"
, connectUser = "test"
, connectPassword = "test"
, connectDatabase = "test"
} .
#else
withSqliteConn ":memory:" . withSqliteConn ":memory:" .
#endif
runSqlConn . runSqlConn .
(runMigrationSilent migrateAll >>) #if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)
(runMigrationSilent migrateAll >>) $ (cleanDB >> act)
#else
(runMigrationSilent migrateAll >>) $ act
#endif