relational-query-0.10.0.0: src/Database/Relational/Monad/Trans/Join.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- |
-- Module : Database.Relational.Monad.Trans.Join
-- Copyright : 2013-2017 Kei Hibino
-- License : BSD3
--
-- Maintainer : ex8k.hibino@gmail.com
-- Stability : experimental
-- Portability : unknown
--
-- This module defines monad transformer which lift to basic 'MonadQuery'.
module Database.Relational.Monad.Trans.Join
( -- * Transformer into join query
QueryJoin, join',
-- * Result
extractProduct,
-- * Unsafe API
unsafeSubQueryWithAttr,
) where
import Prelude hiding (product)
import Control.Monad.Trans.Class (MonadTrans (lift))
import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)
import Control.Monad.Trans.State (modify, StateT, runStateT)
import Control.Applicative (Applicative, (<$>))
import Control.Arrow (second, (***))
import Data.Maybe (fromMaybe)
import Data.Monoid (Last (Last, getLast))
import Database.Relational.Internal.ContextType (Flat)
import Database.Relational.SqlSyntax
(Duplication (All), NodeAttr (Just', Maybe), Predicate, Record,
SubQuery, Qualified, JoinProduct, restrictProduct, growProduct, )
import Database.Relational.Monad.Trans.JoinState
(JoinContext, primeJoinContext, updateProduct, joinProduct)
import qualified Database.Relational.Record as Record
import Database.Relational.Projectable (PlaceHolders, unsafeAddPlaceHolders)
import Database.Relational.Monad.BaseType (ConfigureQuery, qualifyQuery, Relation, untypeRelation)
import Database.Relational.Monad.Class (MonadQualify (..), MonadQuery (..))
-- | 'StateT' type to accumulate join product context.
newtype QueryJoin m a =
QueryJoin (StateT JoinContext (WriterT (Last Duplication) m) a)
deriving (Monad, Functor, Applicative)
instance MonadTrans QueryJoin where
lift = QueryJoin . lift . lift
-- | Lift to 'QueryJoin'
join' :: Monad m => m a -> QueryJoin m a
join' = lift
-- | Unsafely update join product context.
updateContext :: Monad m => (JoinContext -> JoinContext) -> QueryJoin m ()
updateContext = QueryJoin . modify
-- | Add last join product restriction.
updateJoinRestriction :: Monad m => Predicate Flat -> QueryJoin m ()
updateJoinRestriction e = updateContext (updateProduct d) where
d Nothing = error "on: Product is empty! Restrict target product is not found!"
d (Just pt) = restrictProduct pt e
instance MonadQualify q m => MonadQualify q (QueryJoin m) where
liftQualify = join' . liftQualify
-- | Joinable query instance.
instance MonadQuery (QueryJoin ConfigureQuery) where
setDuplication = QueryJoin . lift . tell . Last . Just
restrictJoin = updateJoinRestriction
query' = queryWithAttr Just'
queryMaybe' pr = do
(ph, pj) <- queryWithAttr Maybe pr
return (ph, Record.just pj)
-- | Unsafely join sub-query with this query.
unsafeSubQueryWithAttr :: Monad q
=> NodeAttr -- ^ Attribute maybe or just
-> Qualified SubQuery -- ^ 'SubQuery' to join
-> QueryJoin q (Record c r) -- ^ Result joined context and record of 'SubQuery' result.
unsafeSubQueryWithAttr attr qsub = do
updateContext (updateProduct (`growProduct` (attr, qsub)))
return $ Record.unsafeFromQualifiedSubQuery qsub
-- | Basic monadic join operation using 'MonadQuery'.
queryWithAttr :: NodeAttr
-> Relation p r
-> QueryJoin ConfigureQuery (PlaceHolders p, Record c r)
queryWithAttr attr = unsafeAddPlaceHolders . run where
run rel = do
q <- liftQualify $ do
sq <- untypeRelation rel
qualifyQuery sq
unsafeSubQueryWithAttr attr q
-- | Run 'QueryJoin' to get 'JoinProduct'
extractProduct :: Functor m => QueryJoin m a -> m ((a, JoinProduct), Duplication)
extractProduct (QueryJoin s) = (second joinProduct *** (fromMaybe All . getLast))
<$> runWriterT (runStateT s primeJoinContext)