From e045bedd8f071fb6d642fbe14570ea727a896e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Crist=C3=B3v=C3=A3o?= Date: Wed, 11 Sep 2013 10:29:40 +0100 Subject: [PATCH 01/14] IN works for subList_select assumes ordering. Made ordering explicit. --- test/Test.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Test.hs b/test/Test.hs index 3d92243..908afa1 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -643,7 +643,8 @@ main = do ret <- select $ from $ \p -> do let subquery = - from $ \bp -> + from $ \bp -> do + orderBy [ asc (bp ^. BlogPostAuthorId) ] return (bp ^. BlogPostAuthorId) where_ (p ^. PersonId `in_` subList_select subquery) return p From e8af2a30b624071b8f9ec70f0692b52401b62a32 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 02:08:43 -0300 Subject: [PATCH 02/14] Add tests that expose bug #28. --- test/Test.hs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/Test.hs b/test/Test.hs index 908afa1..47a60be 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -101,6 +101,37 @@ main = do , (p2e, p1e) , (p2e, p2e) ] + it "works for a self-join via sub_select" $ + run $ do + p1k <- insert p1 + p2k <- insert p2 + f1k <- insert (Follow p1k p2k) + f2k <- insert (Follow p2k p1k) + ret <- select $ + from $ \followA -> do + let subquery = + from $ \followB -> do + where_ $ followA ^. FollowFollower ==. followB ^. FollowFollowed + return $ followB ^. FollowFollower + where_ $ followA ^. FollowFollowed ==. sub_select subquery + return followA + liftIO $ length ret `shouldBe` 2 + + it "works for a self-join via exists" $ + run $ do + p1k <- insert p1 + p2k <- insert p2 + f1k <- insert (Follow p1k p2k) + f2k <- insert (Follow p2k p1k) + ret <- select $ + from $ \followA -> do + where_ $ exists $ + from $ \followB -> + where_ $ followA ^. FollowFollower ==. followB ^. FollowFollowed + return followA + liftIO $ length ret `shouldBe` 2 + + it "works for a simple projection" $ run $ do p1k <- insert p1 From e8013a93d4a31ff426680d75645e2262f7485058 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 02:17:29 -0300 Subject: [PATCH 03/14] Saner implementation of existsHelper. --- src/Database/Esqueleto/Internal/Sql.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 910a8c7..911af25 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -392,11 +392,11 @@ sub mode query = ERaw Parens $ \conn -> toRawSql mode pureQuery conn query fromDBName :: Connection -> DBName -> TLB.Builder fromDBName conn = TLB.fromText . connEscapeName conn -existsHelper :: SqlQuery () -> SqlExpr (Value a) -existsHelper = - ERaw Parens . - flip (toRawSql SELECT pureQuery) . - (>> return (val True :: SqlExpr (Value Bool))) +existsHelper :: SqlQuery () -> SqlExpr (Value Bool) +existsHelper = sub SELECT . (>> return true) + where + true :: SqlExpr (Value Bool) + true = val True ifNotEmptyList :: SqlExpr (ValueList a) -> Bool -> SqlExpr (Value Bool) -> SqlExpr (Value Bool) ifNotEmptyList EEmptyList b _ = val b From 53402d726f14c403e0a259b86f4eea6d8a55a019 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 03:27:12 -0300 Subject: [PATCH 04/14] Add some documentaion to SqlExpr. --- src/Database/Esqueleto/Internal/Sql.hs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 911af25..ab40207 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -232,16 +232,35 @@ type Insertion = Proxy -- | An expression on the SQL backend. data SqlExpr a where - EInsert :: Proxy a -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) + -- | An entity, created by 'from' (cf. 'fromStart'). EEntity :: Ident -> SqlExpr (Entity val) + + -- | Just a tag stating that something is nullable. EMaybe :: SqlExpr a -> SqlExpr (Maybe a) + + -- | Raw expression: states whether parenthesis are needed + -- around this expression, and takes information about the SQL + -- connection (mainly for escaping names) and returns both an + -- string ('TLB.Builder') and a list of values to be + -- interpolated by the SQL backend. ERaw :: NeedParens -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) - EList :: SqlExpr (Value a) -> SqlExpr (ValueList a) + + -- | 'EList' and 'EEmptyList' are used by list operators. + EList :: SqlExpr (Value a) -> SqlExpr (ValueList a) EEmptyList :: SqlExpr (ValueList a) + + -- | A 'SqlExpr' accepted only by 'orderBy'. EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy - ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) + + -- | A 'SqlExpr' accepted only by 'set'. + ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) + + -- | An internal 'SqlExpr' used by the 'from' hack. EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a) + -- | Used by 'insertSelect'. + EInsert :: Proxy a -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) + data NeedParens = Parens | Never parensM :: NeedParens -> TLB.Builder -> TLB.Builder From a7d8c99213c6180c051ee942ee7f8739445c762a Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 03:52:10 -0300 Subject: [PATCH 05/14] Re-order exports related to insertSelect. --- src/Database/Esqueleto/Internal/Sql.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index ab40207..4222b63 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -23,6 +23,10 @@ module Database.Esqueleto.Internal.Sql , deleteCount , update , updateCount + , insertSelectDistinct + , insertSelect + , (<#) + , (<&>) -- * The guts , unsafeSqlBinOp , unsafeSqlValue @@ -35,10 +39,6 @@ module Database.Esqueleto.Internal.Sql , Mode(..) , SqlSelect , veryUnsafeCoerceSqlExprValue - , insertSelectDistinct - , insertSelect - , (<#) - , (<&>) ) where import Control.Applicative (Applicative(..), (<$>), (<$)) From c5c76959bd93568b3612e33de2931bc8c4790306 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 04:03:40 -0300 Subject: [PATCH 06/14] Move public insertSelect API to Language module. --- src/Database/Esqueleto/Internal/Language.hs | 11 +++++++ src/Database/Esqueleto/Internal/Sql.hs | 32 +++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Language.hs b/src/Database/Esqueleto/Internal/Language.hs index ebc220d..ac398b2 100644 --- a/src/Database/Esqueleto/Internal/Language.hs +++ b/src/Database/Esqueleto/Internal/Language.hs @@ -27,6 +27,7 @@ module Database.Esqueleto.Internal.Language , OnClauseWithoutMatchingJoinException(..) , OrderBy , Update + , Insertion -- * The guts , JoinKind(..) , IsJoinKind(..) @@ -307,6 +308,12 @@ class (Functor query, Applicative query, Monad query) => (*=.) :: (PersistEntity val, PersistField a) => EntityField val a -> expr (Value a) -> expr (Update val) (/=.) :: (PersistEntity val, PersistField a) => EntityField val a -> expr (Value a) -> expr (Update val) + -- | Apply a 'PersistField' constructor to @expr Value@ arguments. + (<#) :: (a -> b) -> expr (Value a) -> expr (Insertion b) + + -- | Apply extra @expr Value@ arguments to a 'PersistField' constructor + (<&>) :: expr (Insertion (a -> b)) -> expr (Value a) -> expr (Insertion b) + -- Fixity declarations infixl 9 ^. @@ -492,6 +499,10 @@ data OrderBy data Update typ +-- | Phantom type used by 'insertSelect'. +data Insertion a + + -- | @FROM@ clause: bring entities into scope. -- -- This function internally uses two type classes in order to diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 4222b63..1c73b85 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -25,8 +25,6 @@ module Database.Esqueleto.Internal.Sql , updateCount , insertSelectDistinct , insertSelect - , (<#) - , (<&>) -- * The guts , unsafeSqlBinOp , unsafeSqlValue @@ -228,7 +226,6 @@ useIdent conn (I ident) = fromDBName conn $ DBName ident ---------------------------------------------------------------------- -type Insertion = Proxy -- | An expression on the SQL backend. data SqlExpr a where @@ -390,6 +387,13 @@ instance Esqueleto SqlQuery SqlExpr SqlBackend where field *=. expr = setAux field (\ent -> ent ^. field *. expr) field /=. expr = setAux field (\ent -> ent ^. field /. expr) + (<#) _ (ERaw _ f) = EInsert Proxy f + + (EInsert _ f) <&> (ERaw _ g) = EInsert Proxy $ \x -> + let (fb, fv) = f x + (gb, gv) = g x + in (fb <> ", " <> gb, fv ++ gv) + instance ToSomeValues SqlExpr (SqlExpr (Value a)) where toSomeValues a = [SomeValue a] @@ -893,10 +897,11 @@ class SqlSelect a r | a -> r, r -> a where -- | You may return an insertion of some PersistEntity instance PersistEntity a => SqlSelect (SqlExpr (Insertion a)) (Insertion a) where - sqlSelectCols esc (EInsert _ f) = let (b, vals) = f esc - in (b, vals) + sqlSelectCols conn (EInsert _ f) = f conn sqlSelectColCount = const 0 - sqlSelectProcessRow = const (Right Proxy) + sqlSelectProcessRow = const (Right (error msg)) + where + msg = "sqlSelectProcessRow/SqlSelect (SqlExpr (Insertion a)) (Insertion a): never here" -- | Not useful for 'select', but used for 'update' and 'delete'. @@ -1446,23 +1451,14 @@ from16P = const Proxy to16 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p)) -> (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) to16 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p)) = (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) --- | Apply a 'PersistField' constructor to @SqlExpr Value@ arguments -(<#) :: (a -> b) -> SqlExpr (Value a) -> SqlExpr (Insertion b) -(<#) _ (ERaw _ f) = EInsert Proxy f --- | Apply extra @SqlExpr Value@ arguments to a 'PersistField' constructor -(<&>) :: SqlExpr (Insertion (a -> b)) -> SqlExpr (Value a) -> SqlExpr (Insertion b) -(EInsert _ f) <&> (ERaw _ g) = EInsert Proxy $ \x-> - let (fb, fv) = f x - (gb, gv) = g x - in (fb <> ", " <> gb, fv ++ gv) - --- | Insert a 'PersistField' for every selected value +-- | Insert a 'PersistField' for every selected value. insertSelect :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) => SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m () insertSelect = insertGeneralSelect SELECT --- | Insert a 'PersistField' for every unique selected value + +-- | Insert a 'PersistField' for every unique selected value. insertSelectDistinct :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) => SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m () insertSelectDistinct = insertGeneralSelect SELECT_DISTINCT From 33b1fafc2d9898740f10597609febcd1c89d37a9 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 04:16:35 -0300 Subject: [PATCH 07/14] Thread IdentState through subqueries (fixes #28). There used to be name clashes if a subquery referenced an entity that was already being used on the outer query. Now we thread the outer query's IdentState to its subqueries, which use it instead of initialIdentState. Note that clashes still may occur between subqueries of a query, but I think that's harmless. --- src/Database/Esqueleto/Internal/Sql.hs | 150 ++++++++++++++----------- 1 file changed, 85 insertions(+), 65 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 1c73b85..205a462 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -35,6 +35,9 @@ module Database.Esqueleto.Internal.Sql , rawEsqueleto , toRawSql , Mode(..) + , IdentState + , initialIdentState + , IdentInfo , SqlSelect , veryUnsafeCoerceSqlExprValue ) where @@ -219,9 +222,13 @@ newIdentFor = Q . lift . try . unDBName return (I t) +-- | Information needed to escape and use identifiers. +type IdentInfo = (Connection, IdentState) + + -- | Use an identifier. -useIdent :: Connection -> Ident -> TLB.Builder -useIdent conn (I ident) = fromDBName conn $ DBName ident +useIdent :: IdentInfo -> Ident -> TLB.Builder +useIdent info (I ident) = fromDBName info $ DBName ident ---------------------------------------------------------------------- @@ -240,7 +247,7 @@ data SqlExpr a where -- connection (mainly for escaping names) and returns both an -- string ('TLB.Builder') and a list of values to be -- interpolated by the SQL backend. - ERaw :: NeedParens -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) + ERaw :: NeedParens -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) -- | 'EList' and 'EEmptyList' are used by list operators. EList :: SqlExpr (Value a) -> SqlExpr (ValueList a) @@ -256,7 +263,7 @@ data SqlExpr a where EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a) -- | Used by 'insertSelect'. - EInsert :: Proxy a -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) + EInsert :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) data NeedParens = Parens | Never @@ -317,7 +324,7 @@ instance Esqueleto SqlQuery SqlExpr SqlBackend where sub_selectDistinct = sub SELECT_DISTINCT EEntity ident ^. field = - ERaw Never $ \conn -> (useIdent conn ident <> ("." <> fieldName conn field), []) + ERaw Never $ \info -> (useIdent info ident <> ("." <> fieldName info field), []) EMaybe r ?. field = maybelize (r ^. field) where @@ -331,10 +338,10 @@ instance Esqueleto SqlQuery SqlExpr SqlBackend where nothing = unsafeSqlValue "NULL" joinV (ERaw p f) = ERaw p f countRows = unsafeSqlValue "COUNT(*)" - count (ERaw _ f) = ERaw Never $ \conn -> let (b, vals) = f conn + count (ERaw _ f) = ERaw Never $ \info -> let (b, vals) = f info in ("COUNT" <> parens b, vals) - not_ (ERaw p f) = ERaw Never $ \conn -> let (b, vals) = f conn + not_ (ERaw p f) = ERaw Never $ \info -> let (b, vals) = f info in ("NOT " <> parensM p b, vals) (==.) = unsafeSqlBinOp " = " @@ -399,21 +406,21 @@ instance ToSomeValues SqlExpr (SqlExpr (Value a)) where toSomeValues a = [SomeValue a] fieldName :: (PersistEntity val, PersistField typ) - => Connection -> EntityField val typ -> TLB.Builder -fieldName conn = fromDBName conn . fieldDB . persistFieldDef + => IdentInfo -> EntityField val typ -> TLB.Builder +fieldName info = fromDBName info . fieldDB . persistFieldDef setAux :: (PersistEntity val, PersistField typ) => EntityField val typ -> (SqlExpr (Entity val) -> SqlExpr (Value typ)) -> SqlExpr (Update val) setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent) - where name = ERaw Never $ \conn -> (fieldName conn field, mempty) + where name = ERaw Never $ \info -> (fieldName info field, mempty) sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a) -sub mode query = ERaw Parens $ \conn -> toRawSql mode pureQuery conn query +sub mode query = ERaw Parens $ \info -> toRawSql mode pureQuery info query -fromDBName :: Connection -> DBName -> TLB.Builder -fromDBName conn = TLB.fromText . connEscapeName conn +fromDBName :: IdentInfo -> DBName -> TLB.Builder +fromDBName (conn, _) = TLB.fromText . connEscapeName conn existsHelper :: SqlQuery () -> SqlExpr (Value Bool) existsHelper = sub SELECT . (>> return true) @@ -444,8 +451,8 @@ ifNotEmptyList (EList _) _ x = x unsafeSqlBinOp :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c) unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f where - f conn = let (b1, vals1) = f1 conn - (b2, vals2) = f2 conn + f info = let (b1, vals1) = f1 info + (b2, vals2) = f2 info in ( parensM p1 b1 <> op <> parensM p2 b2 , vals1 <> vals2 ) {-# INLINE unsafeSqlBinOp #-} @@ -463,9 +470,9 @@ unsafeSqlValue v = ERaw Never $ \_ -> (v, mempty) unsafeSqlFunction :: UnsafeSqlFunctionArgument a => TLB.Builder -> a -> SqlExpr (Value b) unsafeSqlFunction name arg = - ERaw Never $ \conn -> + ERaw Never $ \info -> let (argsTLB, argsVals) = - uncommas' $ map (\(ERaw _ f) -> f conn) $ toArgList arg + uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList arg in (name <> parens argsTLB, argsVals) class UnsafeSqlFunctionArgument a where @@ -527,7 +534,7 @@ rawSelectSource mode query = src run conn = uncurry rawQuery $ first builderToText $ - toRawSql mode pureQuery conn query + toRawSql mode pureQuery (conn, initialIdentState) query massage = do mrow <- C.await @@ -639,7 +646,7 @@ rawEsqueleto mode query = do conn <- SqlPersistT R.ask uncurry rawExecuteCount $ first builderToText $ - toRawSql mode pureQuery conn query + toRawSql mode pureQuery (conn, initialIdentState) query -- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s @@ -723,24 +730,37 @@ builderToText = TL.toStrict . TLB.toLazyTextWith defaultChunkSize -- @esqueleto@, instead of manually using this function (which is -- possible but tedious), you may just turn on query logging of -- @persistent@. -toRawSql :: SqlSelect a r => Mode -> QueryType a -> Connection -> SqlQuery a -> (TLB.Builder, [PersistValue]) -toRawSql mode qt conn query = - let (ret, SideData fromClauses setClauses whereClauses groupByClause havingClause orderByClauses limitClause) = - flip S.evalState initialIdentState $ +toRawSql :: SqlSelect a r => Mode -> QueryType a -> IdentInfo -> SqlQuery a -> (TLB.Builder, [PersistValue]) +toRawSql mode qt (conn, firstIdentState) query = + let ((ret, sd), finalIdentState) = + flip S.runState firstIdentState $ W.runWriterT $ unQ query + SideData fromClauses + setClauses + whereClauses + groupByClause + havingClause + orderByClauses + limitClause = sd + -- Pass the finalIdentState (containing all identifiers + -- that were used) to the subsequent calls. This ensures + -- that no name clashes will occur on subqueries that may + -- appear on the expressions below. + info = (conn, finalIdentState) in mconcat [ makeInsert qt ret - , makeSelect conn mode ret - , makeFrom conn mode fromClauses - , makeSet conn setClauses - , makeWhere conn whereClauses - , makeGroupBy conn groupByClause - , makeHaving conn havingClause - , makeOrderBy conn orderByClauses - , makeLimit conn limitClause + , makeSelect info mode ret + , makeFrom info mode fromClauses + , makeSet info setClauses + , makeWhere info whereClauses + , makeGroupBy info groupByClause + , makeHaving info havingClause + , makeOrderBy info orderByClauses + , makeLimit info limitClause ] + -- | (Internal) Mode of query being converted by 'toRawSql'. data Mode = SELECT | SELECT_DISTINCT | DELETE | UPDATE @@ -767,21 +787,21 @@ uncommas' :: Monoid a => [(TLB.Builder, a)] -> (TLB.Builder, a) uncommas' = (uncommas *** mconcat) . unzip -makeSelect :: SqlSelect a r => Connection -> Mode -> a -> (TLB.Builder, [PersistValue]) -makeSelect conn mode ret = +makeSelect :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue]) +makeSelect info mode ret = case mode of SELECT -> withCols "SELECT " SELECT_DISTINCT -> withCols "SELECT DISTINCT " DELETE -> plain "DELETE " UPDATE -> plain "UPDATE " where - withCols v = first (v <>) (sqlSelectCols conn ret) + withCols v = first (v <>) (sqlSelectCols info ret) plain v = (v, []) -makeFrom :: Connection -> Mode -> [FromClause] -> (TLB.Builder, [PersistValue]) +makeFrom :: IdentInfo -> Mode -> [FromClause] -> (TLB.Builder, [PersistValue]) makeFrom _ _ [] = mempty -makeFrom conn mode fs = ret +makeFrom info mode fs = ret where ret = case collectOnClauses fs of Left expr -> throw $ mkExc expr @@ -802,8 +822,8 @@ makeFrom conn mode fs = ret base ident@(I identText) def = let db@(DBName dbText) = entityDB def in ( if dbText == identText - then fromDBName conn db - else fromDBName conn db <> (" AS " <> useIdent conn ident) + then fromDBName info db + else fromDBName info db <> (" AS " <> useIdent info ident) , mempty ) fromKind InnerJoinKind = " INNER JOIN " @@ -812,56 +832,56 @@ makeFrom conn mode fs = ret fromKind RightOuterJoinKind = " RIGHT OUTER JOIN " fromKind FullOuterJoinKind = " FULL OUTER JOIN " - makeOnClause (ERaw _ f) = first (" ON " <>) (f conn) + makeOnClause (ERaw _ f) = first (" ON " <>) (f info) mkExc :: SqlExpr (Value Bool) -> OnClauseWithoutMatchingJoinException mkExc (ERaw _ f) = OnClauseWithoutMatchingJoinException $ - TL.unpack $ TLB.toLazyText $ fst (f conn) + TL.unpack $ TLB.toLazyText $ fst (f info) -makeSet :: Connection -> [SetClause] -> (TLB.Builder, [PersistValue]) +makeSet :: IdentInfo -> [SetClause] -> (TLB.Builder, [PersistValue]) makeSet _ [] = mempty -makeSet conn os = first ("\nSET " <>) $ uncommas' (map mk os) +makeSet info os = first ("\nSET " <>) $ uncommas' (map mk os) where - mk (SetClause (ERaw _ f)) = f conn + mk (SetClause (ERaw _ f)) = f info -makeWhere :: Connection -> WhereClause -> (TLB.Builder, [PersistValue]) +makeWhere :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue]) makeWhere _ NoWhere = mempty -makeWhere conn (Where (ERaw _ f)) = first ("\nWHERE " <>) (f conn) +makeWhere info (Where (ERaw _ f)) = first ("\nWHERE " <>) (f info) -makeGroupBy :: Connection -> GroupByClause -> (TLB.Builder, [PersistValue]) +makeGroupBy :: IdentInfo -> GroupByClause -> (TLB.Builder, [PersistValue]) makeGroupBy _ (GroupBy []) = (mempty, []) -makeGroupBy conn (GroupBy fields) = first ("\nGROUP BY " <>) build +makeGroupBy info (GroupBy fields) = first ("\nGROUP BY " <>) build where - build = uncommas' $ map (\(SomeValue (ERaw _ f)) -> f conn) fields + build = uncommas' $ map (\(SomeValue (ERaw _ f)) -> f info) fields -makeHaving :: Connection -> WhereClause -> (TLB.Builder, [PersistValue]) +makeHaving :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue]) makeHaving _ NoWhere = mempty -makeHaving conn (Where (ERaw _ f)) = first ("\nHAVING " <>) (f conn) +makeHaving info (Where (ERaw _ f)) = first ("\nHAVING " <>) (f info) -makeOrderBy :: Connection -> [OrderByClause] -> (TLB.Builder, [PersistValue]) +makeOrderBy :: IdentInfo -> [OrderByClause] -> (TLB.Builder, [PersistValue]) makeOrderBy _ [] = mempty -makeOrderBy conn os = first ("\nORDER BY " <>) $ uncommas' (map mk os) +makeOrderBy info os = first ("\nORDER BY " <>) $ uncommas' (map mk os) where - mk (EOrderBy t (ERaw p f)) = first ((<> orderByType t) . parensM p) (f conn) + mk (EOrderBy t (ERaw p f)) = first ((<> orderByType t) . parensM p) (f info) orderByType ASC = " ASC" orderByType DESC = " DESC" -makeLimit :: Connection -> LimitClause -> (TLB.Builder, [PersistValue]) +makeLimit :: IdentInfo -> LimitClause -> (TLB.Builder, [PersistValue]) makeLimit _ (Limit Nothing Nothing) = mempty makeLimit _ (Limit Nothing (Just 0)) = mempty -makeLimit conn (Limit ml mo) = (ret, mempty) +makeLimit info (Limit ml mo) = (ret, mempty) where ret = TLB.singleton '\n' <> (limitTLB <> offsetTLB) limitTLB = case ml of Just l -> "LIMIT " <> TLBI.decimal l - Nothing -> TLB.fromText (connNoLimit conn) + Nothing -> TLB.fromText (connNoLimit $ fst info) offsetTLB = case mo of @@ -886,7 +906,7 @@ class SqlSelect a r | a -> r, r -> a where -- | Creates the variable part of the @SELECT@ query and -- returns the list of 'PersistValue's that will be given to -- 'rawQuery'. - sqlSelectCols :: Connection -> a -> (TLB.Builder, [PersistValue]) + sqlSelectCols :: IdentInfo -> a -> (TLB.Builder, [PersistValue]) -- | Number of columns that will be consumed. sqlSelectColCount :: Proxy a -> Int @@ -897,7 +917,7 @@ class SqlSelect a r | a -> r, r -> a where -- | You may return an insertion of some PersistEntity instance PersistEntity a => SqlSelect (SqlExpr (Insertion a)) (Insertion a) where - sqlSelectCols conn (EInsert _ f) = f conn + sqlSelectCols info (EInsert _ f) = f info sqlSelectColCount = const 0 sqlSelectProcessRow = const (Right (error msg)) where @@ -913,10 +933,10 @@ instance SqlSelect () () where -- | You may return an 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where - sqlSelectCols conn expr@(EEntity ident) = ret + sqlSelectCols info expr@(EEntity ident) = ret where process ed = uncommas $ - map ((name <>) . fromDBName conn) $ + map ((name <>) . fromDBName info) $ (entityID ed:) $ map fieldDB $ entityFields ed @@ -926,7 +946,7 @@ instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where -- clause), while 'rawSql' assumes that it's just the -- name of the table (which doesn't allow self-joins, for -- example). - name = useIdent conn ident <> "." + name = useIdent info ident <> "." ret = let ed = entityDef $ getEntityVal $ return expr in (process ed, mempty) sqlSelectColCount = (+1) . length . entityFields . entityDef . getEntityVal @@ -941,7 +961,7 @@ getEntityVal = const Proxy -- | You may return a possibly-@NULL@ 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where - sqlSelectCols conn (EMaybe ent) = sqlSelectCols conn ent + sqlSelectCols info (EMaybe ent) = sqlSelectCols info ent sqlSelectColCount = sqlSelectColCount . fromEMaybe where fromEMaybe :: Proxy (SqlExpr (Maybe e)) -> Proxy (SqlExpr e) @@ -954,8 +974,8 @@ instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entit -- | You may return any single value (i.e. a single column) from -- a 'select' query. instance PersistField a => SqlSelect (SqlExpr (Value a)) (Value a) where - sqlSelectCols esc (ERaw p f) = let (b, vals) = f esc - in (parensM p b, vals) + sqlSelectCols info (ERaw p f) = let (b, vals) = f info + in (parensM p b, vals) sqlSelectColCount = const 1 sqlSelectProcessRow [pv] = Value <$> fromPersistValue pv sqlSelectProcessRow _ = Left "SqlSelect (Value a): wrong number of columns." @@ -1468,4 +1488,4 @@ insertGeneralSelect :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr ( Mode -> SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m () insertGeneralSelect mode query = do conn <- SqlPersistT R.ask - uncurry rawExecute $ first builderToText $ toRawSql mode insertQuery conn query + uncurry rawExecute $ first builderToText $ toRawSql mode insertQuery (conn, initialIdentState) query From 7af17770d025297741c4f7cbf4f812d73a12e2c2 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 04:16:55 -0300 Subject: [PATCH 08/14] Bump version to 1.3.4. --- esqueleto.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esqueleto.cabal b/esqueleto.cabal index f721f44..5c232fe 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -1,5 +1,5 @@ name: esqueleto -version: 1.3.3 +version: 1.3.4 synopsis: Bare bones, type-safe EDSL for SQL queries on persistent backends. homepage: https://github.com/meteficha/esqueleto license: BSD3 From f2c2a4ff2e8c9ab73fe6ba1248b5e93f9d0de1d4 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 15 Sep 2013 14:13:49 +0300 Subject: [PATCH 09/14] Fix Haddocks --- src/Database/Esqueleto/Internal/Sql.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 205a462..e4156cd 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -236,33 +236,33 @@ useIdent info (I ident) = fromDBName info $ DBName ident -- | An expression on the SQL backend. data SqlExpr a where - -- | An entity, created by 'from' (cf. 'fromStart'). + -- An entity, created by 'from' (cf. 'fromStart'). EEntity :: Ident -> SqlExpr (Entity val) - -- | Just a tag stating that something is nullable. + -- Just a tag stating that something is nullable. EMaybe :: SqlExpr a -> SqlExpr (Maybe a) - -- | Raw expression: states whether parenthesis are needed + -- Raw expression: states whether parenthesis are needed -- around this expression, and takes information about the SQL -- connection (mainly for escaping names) and returns both an -- string ('TLB.Builder') and a list of values to be -- interpolated by the SQL backend. ERaw :: NeedParens -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) - -- | 'EList' and 'EEmptyList' are used by list operators. + -- 'EList' and 'EEmptyList' are used by list operators. EList :: SqlExpr (Value a) -> SqlExpr (ValueList a) EEmptyList :: SqlExpr (ValueList a) - -- | A 'SqlExpr' accepted only by 'orderBy'. + -- A 'SqlExpr' accepted only by 'orderBy'. EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy - -- | A 'SqlExpr' accepted only by 'set'. + -- A 'SqlExpr' accepted only by 'set'. ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) - -- | An internal 'SqlExpr' used by the 'from' hack. + -- An internal 'SqlExpr' used by the 'from' hack. EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a) - -- | Used by 'insertSelect'. + -- Used by 'insertSelect'. EInsert :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a) data NeedParens = Parens | Never From 1dd2e045b337407b70114b4b42b701a810334f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Crist=C3=B3v=C3=A3o?= Date: Sun, 15 Sep 2013 15:47:39 +0100 Subject: [PATCH 10/14] Added support for PostgreSQL and MySQL/MariaDB. --- esqueleto.cabal | 25 +++++++++++++++ test/Test.hs | 85 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/esqueleto.cabal b/esqueleto.cabal index f721f44..c91a535 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -47,6 +47,14 @@ source-repository head type: 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 exposed-modules: Database.Esqueleto @@ -90,3 +98,20 @@ test-suite test -- This library , 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 + diff --git a/test/Test.hs b/test/Test.hs index 908afa1..1ee3ee2 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -10,6 +10,7 @@ , TemplateHaskell , TypeFamilies , ScopedTypeVariables + , CPP #-} module Main (main) where @@ -20,11 +21,20 @@ import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT, runNoLoggingT) import Control.Monad.Trans.Control (MonadBaseControl(..)) import Database.Esqueleto 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 Test.Hspec import qualified Data.Conduit as C import qualified Data.Set as S +import qualified Data.List as L -- Test schema @@ -43,6 +53,10 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase| deriving Eq Show |] +-- | this could be achieved with S.fromList, but not all lists +-- have Ord instances +sameElements :: Eq a => [a] -> [a] -> Bool +sameElements l1 l2 = null (l1 L.\\ l2) main :: IO () main = do @@ -96,10 +110,10 @@ main = do ret <- select $ from $ \(person1, person2) -> return (person1, person2) - liftIO $ ret `shouldBe` [ (p1e, p1e) - , (p1e, p2e) - , (p2e, p1e) - , (p2e, p2e) ] + liftIO $ ret `shouldSatisfy` sameElements [ (p1e, p1e) + , (p1e, p2e) + , (p2e, p1e) + , (p2e, p2e) ] it "works for a simple projection" $ run $ do @@ -118,7 +132,8 @@ main = do ret <- select $ from $ \(pa, pb) -> return (pa ^. PersonName, pb ^. PersonName) - liftIO $ ret `shouldBe` [ (Value (personName p1), Value (personName p1)) + liftIO $ ret `shouldSatisfy` sameElements + [ (Value (personName p1), Value (personName p1)) , (Value (personName p1), Value (personName p2)) , (Value (personName p2), Value (personName p1)) , (Value (personName p2), Value (personName p2)) ] @@ -312,7 +327,11 @@ main = do it "works with random_" $ run $ do +#if defined(WITH_POSTGRESQL) || defined(WITH_MYSQL) + ret <- select $ return (random_ :: SqlExpr (Value Double)) +#else ret <- select $ return (random_ :: SqlExpr (Value Int)) +#endif return () it "works with round_" $ @@ -431,7 +450,13 @@ main = do from $ \p -> do orderBy [asc (p ^. PersonAge), asc (p ^. PersonName)] 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 ] +#endif it "works with one ASC and one DESC field" $ run $ do @@ -443,7 +468,11 @@ main = do from $ \p -> do orderBy [desc (p ^. PersonAge), asc (p ^. PersonName)] return p +#ifdef WITH_POSTGRESQL + liftIO $ ret `shouldBe` [ p2e, p1e, p4e, p3e ] +#else liftIO $ ret `shouldBe` [ p1e, p4e, p3e, p2e ] +#endif it "works with a sub_select" $ run $ do @@ -547,10 +576,27 @@ main = do from $ \p -> do orderBy [ asc (p ^. PersonName), asc (p ^. PersonAge) ] 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 $ ret `shouldBe` [ Entity p2k (Person anon Nothing) , Entity p1k (Person anon (Just 73)) , Entity p3k p3 ] +#endif it "works with a subexpression having COUNT(*)" $ run $ do @@ -724,6 +770,16 @@ insert' v = flip Entity v <$> insert v type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m , C.MonadUnsafeIO m, C.MonadThrow m ) +#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL) +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 runSilent act = runNoLoggingT $ run_worker act @@ -739,8 +795,23 @@ verbose = True run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a -run_worker = +run_worker act = C.runResourceT . +#if defined(WITH_POSTGRESQL) + withPostgresqlConn "host=localhost port=5432 user=test dbname=test" . +#elif defined (WITH_MYSQL) + withMySQLConn defaultConnectInfo + { connectHost = "localhost" + , connectUser = "test" + , connectPassword = "test" + , connectDatabase = "test" + } . +#else withSqliteConn ":memory:" . +#endif runSqlConn . - (runMigrationSilent migrateAll >>) +#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL) + (runMigrationSilent migrateAll >>) $ (cleanDB >> act) +#else + (runMigrationSilent migrateAll >>) $ act +#endif From 5ff30e7c5c7f89c31a657a4f3d9f9542745c069f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Crist=C3=B3v=C3=A3o?= Date: Sun, 15 Sep 2013 15:58:13 +0100 Subject: [PATCH 11/14] Minor changes --- test/Test.hs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/Test.hs b/test/Test.hs index 1ee3ee2..7aa7fd6 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -27,7 +27,9 @@ import Database.Persist.Postgresql (withPostgresqlConn) import Database.Persist.MySQL ( withMySQLConn , connectHost , connectDatabase - ,connectUser,connectPassword,defaultConnectInfo) + , connectUser + , connectPassword + , defaultConnectInfo) #endif import Database.Persist.TH import Test.Hspec @@ -55,8 +57,8 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase| -- | this could be achieved with S.fromList, but not all lists -- have Ord instances -sameElements :: Eq a => [a] -> [a] -> Bool -sameElements l1 l2 = null (l1 L.\\ l2) +sameElementsAs :: Eq a => [a] -> [a] -> Bool +sameElementsAs l1 l2 = null (l1 L.\\ l2) main :: IO () main = do @@ -110,10 +112,10 @@ main = do ret <- select $ from $ \(person1, person2) -> return (person1, person2) - liftIO $ ret `shouldSatisfy` sameElements [ (p1e, p1e) - , (p1e, p2e) - , (p2e, p1e) - , (p2e, p2e) ] + liftIO $ ret `shouldSatisfy` sameElementsAs [ (p1e, p1e) + , (p1e, p2e) + , (p2e, p1e) + , (p2e, p2e) ] it "works for a simple projection" $ run $ do @@ -132,7 +134,7 @@ main = do ret <- select $ from $ \(pa, pb) -> return (pa ^. PersonName, pb ^. PersonName) - liftIO $ ret `shouldSatisfy` sameElements + liftIO $ ret `shouldSatisfy` sameElementsAs [ (Value (personName p1), Value (personName p1)) , (Value (personName p1), Value (personName p2)) , (Value (personName p2), Value (personName p1)) @@ -771,6 +773,10 @@ type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger 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) ()) @@ -798,7 +804,7 @@ run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a run_worker act = C.runResourceT . #if defined(WITH_POSTGRESQL) - withPostgresqlConn "host=localhost port=5432 user=test dbname=test" . + withPostgresqlConn "host=localhost port=5432 user=joao dbname=esqueleto" . #elif defined (WITH_MYSQL) withMySQLConn defaultConnectInfo { connectHost = "localhost" From a5c2d8dddbbb55800c29eabf1297e826ebc00786 Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 13:00:14 -0300 Subject: [PATCH 12/14] Bump version to 1.3.4.1. --- esqueleto.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esqueleto.cabal b/esqueleto.cabal index 5c232fe..169355b 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -1,5 +1,5 @@ name: esqueleto -version: 1.3.4 +version: 1.3.4.1 synopsis: Bare bones, type-safe EDSL for SQL queries on persistent backends. homepage: https://github.com/meteficha/esqueleto license: BSD3 From 4e6f25e656430cea378a5f8e4de707633a29d28f Mon Sep 17 00:00:00 2001 From: Felipe Lessa Date: Sun, 15 Sep 2013 13:00:57 -0300 Subject: [PATCH 13/14] Bump version to 1.3.4.2. --- esqueleto.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esqueleto.cabal b/esqueleto.cabal index 169355b..1a892f4 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -1,5 +1,5 @@ name: esqueleto -version: 1.3.4.1 +version: 1.3.4.2 synopsis: Bare bones, type-safe EDSL for SQL queries on persistent backends. homepage: https://github.com/meteficha/esqueleto license: BSD3 From 42592501cde248cc780894ee459c1e48c9d66178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Crist=C3=B3v=C3=A3o?= Date: Sun, 15 Sep 2013 18:00:14 +0100 Subject: [PATCH 14/14] Fixed typos. --- test/Test.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Test.hs b/test/Test.hs index 7aa7fd6..5f98681 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -804,7 +804,7 @@ run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a run_worker act = C.runResourceT . #if defined(WITH_POSTGRESQL) - withPostgresqlConn "host=localhost port=5432 user=joao dbname=esqueleto" . + withPostgresqlConn "host=localhost port=5432 user=test dbname=test" . #elif defined (WITH_MYSQL) withMySQLConn defaultConnectInfo { connectHost = "localhost"