204 lines
7.7 KiB
Haskell
204 lines
7.7 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE FunctionalDependencies #-}
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
module Database.Esqueleto.Experimental.From.SqlSetOperation
|
|
where
|
|
|
|
import Control.Monad.Trans.Class (lift)
|
|
import qualified Control.Monad.Trans.State as S
|
|
import qualified Control.Monad.Trans.Writer as W
|
|
import Database.Esqueleto.Experimental.From
|
|
import Database.Esqueleto.Experimental.ToAlias
|
|
import Database.Esqueleto.Experimental.ToAliasReference
|
|
import Database.Esqueleto.Internal.Internal hiding (From(..), from, on)
|
|
import Database.Esqueleto.Internal.PersistentImport (DBName(..))
|
|
|
|
data SqlSetOperation a
|
|
= SqlSetUnion (SqlSetOperation a) (SqlSetOperation a)
|
|
| SqlSetUnionAll (SqlSetOperation a) (SqlSetOperation a)
|
|
| SqlSetExcept (SqlSetOperation a) (SqlSetOperation a)
|
|
| SqlSetIntersect (SqlSetOperation a) (SqlSetOperation a)
|
|
| SelectQueryP NeedParens (SqlQuery a)
|
|
|
|
runSetOperation :: (SqlSelect a r, ToAlias a, ToAliasReference a)
|
|
=> SqlSetOperation a -> SqlQuery (a, FromClause)
|
|
runSetOperation operation = do
|
|
(aliasedOperation, ret) <- aliasQueries operation
|
|
ident <- newIdentFor (DBName "u")
|
|
ref <- toAliasReference ident ret
|
|
pure (ref, FromQuery ident (operationToSql aliasedOperation) NormalSubQuery)
|
|
|
|
where
|
|
aliasQueries o =
|
|
case o of
|
|
SelectQueryP p q -> do
|
|
(ret, sideData) <- Q $ W.censor (\_ -> mempty) $ W.listen $ unQ q
|
|
prevState <- Q $ lift S.get
|
|
aliasedRet <- toAlias ret
|
|
Q $ lift $ S.put prevState
|
|
let p' =
|
|
case p of
|
|
Parens -> Parens
|
|
Never ->
|
|
if (sdLimitClause sideData) /= mempty
|
|
|| length (sdOrderByClause sideData) > 0 then
|
|
Parens
|
|
else
|
|
Never
|
|
pure (SelectQueryP p' $ Q $ W.WriterT $ pure (aliasedRet, sideData), aliasedRet)
|
|
SqlSetUnion o1 o2 -> do
|
|
(o1', ret) <- aliasQueries o1
|
|
(o2', _ ) <- aliasQueries o2
|
|
pure (SqlSetUnion o1' o2', ret)
|
|
SqlSetUnionAll o1 o2 -> do
|
|
(o1', ret) <- aliasQueries o1
|
|
(o2', _ ) <- aliasQueries o2
|
|
pure (SqlSetUnionAll o1' o2', ret)
|
|
SqlSetExcept o1 o2 -> do
|
|
(o1', ret) <- aliasQueries o1
|
|
(o2', _ ) <- aliasQueries o2
|
|
pure (SqlSetExcept o1' o2', ret)
|
|
SqlSetIntersect o1 o2 -> do
|
|
(o1', ret) <- aliasQueries o1
|
|
(o2', _ ) <- aliasQueries o2
|
|
pure (SqlSetIntersect o1' o2', ret)
|
|
|
|
operationToSql o info =
|
|
case o of
|
|
SelectQueryP p q ->
|
|
let (builder, values) = toRawSql SELECT info q
|
|
in (parensM p builder, values)
|
|
SqlSetUnion o1 o2 -> doSetOperation "UNION" info o1 o2
|
|
SqlSetUnionAll o1 o2 -> doSetOperation "UNION ALL" info o1 o2
|
|
SqlSetExcept o1 o2 -> doSetOperation "EXCEPT" info o1 o2
|
|
SqlSetIntersect o1 o2 -> doSetOperation "INTERSECT" info o1 o2
|
|
|
|
doSetOperation operationText info o1 o2 =
|
|
let (q1, v1) = operationToSql o1 info
|
|
(q2, v2) = operationToSql o2 info
|
|
in (q1 <> " " <> operationText <> " " <> q2, v1 <> v2)
|
|
|
|
|
|
{-# DEPRECATED Union "/Since: 3.4.0.0/ - Use the 'union_' function instead of the 'Union' data constructor" #-}
|
|
data Union a b = a `Union` b
|
|
|
|
-- | @UNION@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
|
|
union_ :: a -> b -> Union a b
|
|
union_ = Union
|
|
|
|
{-# DEPRECATED UnionAll "/Since: 3.4.0.0/ - Use the 'unionAll_' function instead of the 'UnionAll' data constructor" #-}
|
|
data UnionAll a b = a `UnionAll` b
|
|
|
|
-- | @UNION@ @ALL@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
|
|
unionAll_ :: a -> b -> UnionAll a b
|
|
unionAll_ = UnionAll
|
|
|
|
{-# DEPRECATED Except "/Since: 3.4.0.0/ - Use the 'except_' function instead of the 'Except' data constructor" #-}
|
|
data Except a b = a `Except` b
|
|
|
|
-- | @EXCEPT@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
|
|
except_ :: a -> b -> Except a b
|
|
except_ = Except
|
|
|
|
{-# DEPRECATED Intersect "/Since: 3.4.0.0/ - Use the 'intersect_' function instead of the 'Intersect' data constructor" #-}
|
|
data Intersect a b = a `Intersect` b
|
|
|
|
-- | @INTERSECT@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
|
|
intersect_ :: a -> b -> Intersect a b
|
|
intersect_ = Intersect
|
|
|
|
class SetOperationT a ~ b => ToSetOperation a b | a -> b where
|
|
type SetOperationT a
|
|
toSetOperation :: a -> SqlSetOperation b
|
|
instance ToSetOperation (SqlSetOperation a) a where
|
|
type SetOperationT (SqlSetOperation a) = a
|
|
toSetOperation = id
|
|
instance ToSetOperation (SqlQuery a) a where
|
|
type SetOperationT (SqlQuery a) = a
|
|
toSetOperation = SelectQueryP Never
|
|
instance (ToSetOperation a c, ToSetOperation b c) => ToSetOperation (Union a b) c where
|
|
type SetOperationT (Union a b) = SetOperationT a
|
|
toSetOperation (Union a b) = SqlSetUnion (toSetOperation a) (toSetOperation b)
|
|
instance (ToSetOperation a c, ToSetOperation b c) => ToSetOperation (UnionAll a b) c where
|
|
type SetOperationT (UnionAll a b) = SetOperationT a
|
|
toSetOperation (UnionAll a b) = SqlSetUnionAll (toSetOperation a) (toSetOperation b)
|
|
instance (ToSetOperation a c, ToSetOperation b c) => ToSetOperation (Except a b) c where
|
|
type SetOperationT (Except a b) = SetOperationT a
|
|
toSetOperation (Except a b) = SqlSetExcept (toSetOperation a) (toSetOperation b)
|
|
instance (ToSetOperation a c, ToSetOperation b c) => ToSetOperation (Intersect a b) c where
|
|
type SetOperationT (Intersect a b) = SetOperationT a
|
|
toSetOperation (Intersect a b) = SqlSetIntersect (toSetOperation a) (toSetOperation b)
|
|
|
|
{-# DEPRECATED SelectQuery "/Since: 3.4.0.0/ - It is no longer necessary to tag 'SqlQuery' values with @SelectQuery@" #-}
|
|
pattern SelectQuery :: SqlQuery a -> SqlSetOperation a
|
|
pattern SelectQuery q = SelectQueryP Never q
|
|
|
|
instance
|
|
( SqlSelect c r
|
|
, ToAlias c
|
|
, ToAliasReference c
|
|
, ToSetOperation a c
|
|
, ToSetOperation b c
|
|
, c ~ SetOperationT a
|
|
)
|
|
=>
|
|
From (Union a b)
|
|
where
|
|
type FromT (Union a b) = SetOperationT a
|
|
runFrom u = runSetOperation $ toSetOperation u
|
|
|
|
instance
|
|
( SqlSelect c r
|
|
, ToAlias c
|
|
, ToAliasReference c
|
|
, ToSetOperation a c
|
|
, ToSetOperation b c
|
|
, c ~ SetOperationT a
|
|
)
|
|
=>
|
|
From (UnionAll a b)
|
|
where
|
|
type FromT (UnionAll a b) = SetOperationT a
|
|
runFrom u = runSetOperation $ toSetOperation u
|
|
|
|
instance
|
|
( SqlSelect c r
|
|
, ToAlias c
|
|
, ToAliasReference c
|
|
, ToSetOperation a c
|
|
, ToSetOperation b c
|
|
, c ~ SetOperationT a
|
|
)
|
|
=>
|
|
From (Intersect a b)
|
|
where
|
|
type FromT (Intersect a b) = SetOperationT a
|
|
runFrom u = runSetOperation $ toSetOperation u
|
|
|
|
instance
|
|
( SqlSelect c r
|
|
, ToAlias c
|
|
, ToAliasReference c
|
|
, ToSetOperation a c
|
|
, ToSetOperation b c
|
|
, c ~ SetOperationT a
|
|
)
|
|
=>
|
|
From (Except a b)
|
|
where
|
|
type FromT (Except a b) = SetOperationT a
|
|
runFrom u = runSetOperation $ toSetOperation u
|
|
|
|
instance (SqlSelect a r, ToAlias a, ToAliasReference a) => From (SqlSetOperation a) where
|
|
type FromT (SqlSetOperation a) = a
|
|
-- If someone uses just a plain SelectQuery it should behave like a normal subquery
|
|
runFrom (SelectQueryP _ subquery) = fromSubQuery NormalSubQuery subquery
|
|
-- Otherwise use the SqlSetOperation
|
|
runFrom u = runSetOperation $ toSetOperation u
|