Steal code from rawSql and stop using it.

Unfortunately we can't use rawSql, since we need explicit control
of the SELECT (i.e. we can't use "??").
This commit is contained in:
Felipe Lessa 2012-09-03 18:43:17 -03:00
parent 2986d0996e
commit 531ca905fd
2 changed files with 170 additions and 61 deletions

View File

@ -26,6 +26,6 @@ library
, monad-supply == 0.3.* , monad-supply == 0.3.*
, monad-logger , monad-logger
, resourcet , conduit
hs-source-dirs: src/ hs-source-dirs: src/
ghc-options: -Wall ghc-options: -Wall

View File

@ -7,19 +7,23 @@ module Database.Esqueleto.Internal.Sql
) where ) where
import Control.Applicative (Applicative(..), (<$>)) import Control.Applicative (Applicative(..), (<$>))
import Control.Arrow (first, (&&&)) import Control.Arrow (first)
import Control.Monad (ap) import Control.Monad (ap)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Logger (MonadLogger) import Control.Monad.Logger (MonadLogger)
import Control.Monad.Trans.Resource (MonadResourceBase)
import Data.List (intersperse) import Data.List (intersperse)
import Data.Monoid (Monoid(..), (<>)) import Data.Monoid (Monoid(..), (<>))
import Database.Persist.EntityDef import Database.Persist.EntityDef
import Database.Persist.GenericSql import Database.Persist.GenericSql
import Database.Persist.GenericSql.Internal (Connection(escapeName)) import Database.Persist.GenericSql.Internal (Connection(escapeName))
import Database.Persist.GenericSql.Raw (withStmt)
import Database.Persist.Store import Database.Persist.Store
import qualified Control.Monad.Supply as S import qualified Control.Monad.Supply as S
import qualified Control.Monad.Trans.Reader as R import qualified Control.Monad.Trans.Reader as R
import qualified Control.Monad.Trans.Writer as W import qualified Control.Monad.Trans.Writer as W
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.Builder as TLB
@ -57,7 +61,7 @@ instance Monoid SideData where
data FromClause = From Ident EntityDef data FromClause = From Ident EntityDef
-- | A complere @WHERE@ clause. -- | A complete @WHERE@ clause.
data WhereClause = Where (SqlExpr (Single Bool)) data WhereClause = Where (SqlExpr (Single Bool))
| NoWhere | NoWhere
@ -69,7 +73,7 @@ instance Monoid WhereClause where
-- | Identifier used for tables. -- | Identifier used for tables.
newtype Ident = I TLB.Builder newtype Ident = I TLB.Builder
-- | Infinite list of identifiers. -- | Infinite list of identifiers.
@ -138,18 +142,44 @@ binop op (ERaw f1) (ERaw f2) = ERaw f
, vals1 <> vals2 ) , vals1 <> vals2 )
-- | Execute an Esqueleto's 'SqlQuery' inside @persistent@'s
-- 'SqlPersist' monad.
selectSource :: ( SqlSelect a r
, C.MonadResource m
, MonadLogger m
, MonadIO m )
=> SqlQuery a -> SqlPersist m (C.Source (SqlPersist m) r)
selectSource query = src
where
src = do
conn <- getConnection
return $ run conn C.$= massage
run conn =
uncurry withStmt $
first (TL.toStrict . TLB.toLazyText) $
toRawSelectSql (fromDBName conn) query
massage = do
mrow <- C.await
case process <$> mrow of
Just (Right r) -> C.yield r >> massage
Just (Left err) -> fail (T.unpack err)
Nothing -> return ()
process = sqlSelectProcessRow
-- | Execute an Esqueleto's 'SqlQuery' inside @persistent@'s -- | Execute an Esqueleto's 'SqlQuery' inside @persistent@'s
-- 'SqlPersist' monad. -- 'SqlPersist' monad.
select :: ( SqlSelect a r select :: ( SqlSelect a r
, RawSql r , C.MonadResource m
, MonadLogger m , MonadLogger m
, MonadResourceBase m) , MonadIO m )
=> SqlQuery a -> SqlPersist m [r] => SqlQuery a -> SqlPersist m [r]
select query = do select query = do
conn <- getConnection src <- selectSource query
uncurry rawSql $ src C.$$ CL.consume
first (TL.toStrict . TLB.toLazyText) $
toRawSelectSql (fromDBName conn) query
-- | Get current database 'Connection'. -- | Get current database 'Connection'.
@ -165,8 +195,8 @@ toRawSelectSql esc query =
W.runWriterT $ W.runWriterT $
unQ query unQ query
(selectText, selectVars) = makeSelect esc ret (_, selectText, selectVars) = sqlSelectCols esc ret
(whereText, whereVars) = makeWhere esc whereClauses ( whereText, whereVars) = makeWhere esc whereClauses
text = mconcat text = mconcat
[ "SELECT " [ "SELECT "
@ -181,8 +211,10 @@ toRawSelectSql esc query =
uncommas :: [TLB.Builder] -> TLB.Builder uncommas :: [TLB.Builder] -> TLB.Builder
uncommas = mconcat . intersperse ", " uncommas = mconcat . intersperse ", "
uncommas' :: Monoid a => [(TLB.Builder, a)] -> (TLB.Builder, a) uncommas' :: Monoid a => [(Int, TLB.Builder, a)] -> (Int, TLB.Builder, a)
uncommas' = uncommas . map fst &&& mconcat . map snd uncommas' xs =
let (as, bs, cs) = unzip3 xs
in (sum as, uncommas bs, mconcat cs)
makeFrom :: Escape -> [FromClause] -> TLB.Builder makeFrom :: Escape -> [FromClause] -> TLB.Builder
@ -202,47 +234,105 @@ parens b = "(" <> (b <> "(")
-- | Class for mapping results coming from 'SqlQuery' into actual -- | Class for mapping results coming from 'SqlQuery' into actual
-- results. -- results.
class RawSql r => SqlSelect a r | a -> r, r -> a where --
makeSelect :: Escape -> a -> (TLB.Builder, [PersistValue]) -- This looks very similar to @RawSql@, and it is! However,
-- there are some crucial differences and ultimately they're
-- different classes.
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
-- 'withStmt'.
sqlSelectCols :: Escape -> a -> (Int, TLB.Builder, [PersistValue])
-- | Transform a row of the result into the data type.
sqlSelectProcessRow :: [PersistValue] -> Either T.Text r
instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where
makeSelect _ (EEntity _) = ("??", mempty) sqlSelectCols escape expr@(EEntity (I ident)) = ret
where
process ed = uncommas $
map ((name <>) . escape) $
(entityID ed:) $
map fieldDB $
entityFields ed
-- 'name' is the biggest difference between 'RawSql' and
-- 'SqlSelect'. We automatically create names for tables
-- (since it's not the user who's writing the FROM
-- clause), while 'rawSql' assumes that it's just the
-- name of the table (which doesn't allow self-joins, for
-- example).
name = ident <> "."
ret = let ed = entityDef $ getEntityVal expr
in (length (entityFields ed) + 1, process ed, mempty)
getEntityVal :: SqlExpr (Entity a) -> a
getEntityVal = error "Database.Esqueleto.SqlSelect.getEntityVal"
sqlSelectProcessRow (idCol:ent) =
Entity <$> fromPersistValue idCol
<*> fromPersistValues ent
sqlSelectProcessRow _ = Left "SqlSelect (Entity a): wrong number of columns."
instance PersistField a => SqlSelect (SqlExpr (Single a)) (Single a) where instance PersistField a => SqlSelect (SqlExpr (Single a)) (Single a) where
makeSelect esc (ERaw f) = first parens (f esc) sqlSelectCols esc (ERaw f) = let (b, vals) = f esc
in (1, parens b, vals)
sqlSelectProcessRow [pv] = Single <$> fromPersistValue pv
sqlSelectProcessRow _ = Left "SqlSelect (Single a): wrong number of columns."
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
) => SqlSelect (a, b) (ra, rb) where ) => SqlSelect (a, b) (ra, rb) where
makeSelect esc (a, b) = sqlSelectCols esc (a, b) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
] ]
sqlSelectProcessRow =
let x = getType processRow
getType :: SqlSelect a r => (z -> Either y (r,x)) -> a
getType = undefined
(colCountFst, _, _) = sqlSelectCols undefined x
processRow row =
let (rowFst, rowSnd) = splitAt colCountFst row
in (,) <$> sqlSelectProcessRow rowFst
<*> sqlSelectProcessRow rowSnd
in colCountFst `seq` processRow
-- Avoids recalculating 'colCountFst'.
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
, SqlSelect c rc , SqlSelect c rc
) => SqlSelect (a, b, c) (ra, rb, rc) where ) => SqlSelect (a, b, c) (ra, rb, rc) where
makeSelect esc (a, b, c) = sqlSelectCols esc (a, b, c) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
] ]
sqlSelectProcessRow = fmap to3 . sqlSelectProcessRow
to3 :: ((a,b),c) -> (a,b,c)
to3 ((a,b),c) = (a,b,c)
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
, SqlSelect c rc , SqlSelect c rc
, SqlSelect d rd , SqlSelect d rd
) => SqlSelect (a, b, c, d) (ra, rb, rc, rd) where ) => SqlSelect (a, b, c, d) (ra, rb, rc, rd) where
makeSelect esc (a, b, c, d) = sqlSelectCols esc (a, b, c, d) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
, makeSelect esc d , sqlSelectCols esc d
] ]
sqlSelectProcessRow = fmap to4 . sqlSelectProcessRow
to4 :: ((a,b),(c,d)) -> (a,b,c,d)
to4 ((a,b),(c,d)) = (a,b,c,d)
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
@ -250,14 +340,19 @@ instance ( SqlSelect a ra
, SqlSelect d rd , SqlSelect d rd
, SqlSelect e re , SqlSelect e re
) => SqlSelect (a, b, c, d, e) (ra, rb, rc, rd, re) where ) => SqlSelect (a, b, c, d, e) (ra, rb, rc, rd, re) where
makeSelect esc (a, b, c, d, e) = sqlSelectCols esc (a, b, c, d, e) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
, makeSelect esc d , sqlSelectCols esc d
, makeSelect esc e , sqlSelectCols esc e
] ]
sqlSelectProcessRow = fmap to5 . sqlSelectProcessRow
to5 :: ((a,b),(c,d),e) -> (a,b,c,d,e)
to5 ((a,b),(c,d),e) = (a,b,c,d,e)
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
@ -266,15 +361,20 @@ instance ( SqlSelect a ra
, SqlSelect e re , SqlSelect e re
, SqlSelect f rf , SqlSelect f rf
) => SqlSelect (a, b, c, d, e, f) (ra, rb, rc, rd, re, rf) where ) => SqlSelect (a, b, c, d, e, f) (ra, rb, rc, rd, re, rf) where
makeSelect esc (a, b, c, d, e, f) = sqlSelectCols esc (a, b, c, d, e, f) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
, makeSelect esc d , sqlSelectCols esc d
, makeSelect esc e , sqlSelectCols esc e
, makeSelect esc f , sqlSelectCols esc f
] ]
sqlSelectProcessRow = fmap to6 . sqlSelectProcessRow
to6 :: ((a,b),(c,d),(e,f)) -> (a,b,c,d,e,f)
to6 ((a,b),(c,d),(e,f)) = (a,b,c,d,e,f)
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
@ -284,16 +384,21 @@ instance ( SqlSelect a ra
, SqlSelect f rf , SqlSelect f rf
, SqlSelect g rg , SqlSelect g rg
) => SqlSelect (a, b, c, d, e, f, g) (ra, rb, rc, rd, re, rf, rg) where ) => SqlSelect (a, b, c, d, e, f, g) (ra, rb, rc, rd, re, rf, rg) where
makeSelect esc (a, b, c, d, e, f, g) = sqlSelectCols esc (a, b, c, d, e, f, g) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
, makeSelect esc d , sqlSelectCols esc d
, makeSelect esc e , sqlSelectCols esc e
, makeSelect esc f , sqlSelectCols esc f
, makeSelect esc g , sqlSelectCols esc g
] ]
sqlSelectProcessRow = fmap to7 . sqlSelectProcessRow
to7 :: ((a,b),(c,d),(e,f),g) -> (a,b,c,d,e,f,g)
to7 ((a,b),(c,d),(e,f),g) = (a,b,c,d,e,f,g)
instance ( SqlSelect a ra instance ( SqlSelect a ra
, SqlSelect b rb , SqlSelect b rb
@ -304,14 +409,18 @@ instance ( SqlSelect a ra
, SqlSelect g rg , SqlSelect g rg
, SqlSelect h rh , SqlSelect h rh
) => SqlSelect (a, b, c, d, e, f, g, h) (ra, rb, rc, rd, re, rf, rg, rh) where ) => SqlSelect (a, b, c, d, e, f, g, h) (ra, rb, rc, rd, re, rf, rg, rh) where
makeSelect esc (a, b, c, d, e, f, g, h) = sqlSelectCols esc (a, b, c, d, e, f, g, h) =
uncommas' uncommas'
[ makeSelect esc a [ sqlSelectCols esc a
, makeSelect esc b , sqlSelectCols esc b
, makeSelect esc c , sqlSelectCols esc c
, makeSelect esc d , sqlSelectCols esc d
, makeSelect esc e , sqlSelectCols esc e
, makeSelect esc f , sqlSelectCols esc f
, makeSelect esc g , sqlSelectCols esc g
, makeSelect esc h , sqlSelectCols esc h
] ]
sqlSelectProcessRow = fmap to8 . sqlSelectProcessRow
to8 :: ((a,b),(c,d),(e,f),(g,h)) -> (a,b,c,d,e,f,g,h)
to8 ((a,b),(c,d),(e,f),(g,h)) = (a,b,c,d,e,f,g,h)