packages feed

DSH-0.12.0.0: src/Database/DSH/Execute/TH.hs

{-# LANGUAGE TemplateHaskell #-}

module Database.DSH.Execute.TH
    ( mkExecTuple
    , mkTabTupleType
    , mkSegTupleType
    , mkSegmentTupleFun
    , mkConstructTuple
    ) where

import           Language.Haskell.TH
import           Data.List

import           Text.Printf

import           Database.DSH.Common.Impossible
import           Database.DSH.Common.TH
import           Database.DSH.Frontend.TupleTypes
import qualified Database.DSH.Frontend.Internals as DSH

--------------------------------------------------------------------------------
-- Common name definitions

tabTupleConsName :: Int -> Name
tabTupleConsName width = mkName $ printf "TTuple%d" width

segTupleConsName :: Int -> Name
segTupleConsName width = mkName $ printf "STuple%d" width

--------------------------------------------------------------------------------
-- Generate the function that executes queries in a tuple layout

elemTyName :: Int -> Q Name
elemTyName i = newName $ printf "ty%d" i

elemLytName :: Int -> Q (Name, Name)
elemLytName i = (,) <$> newName (printf "lyt%d" i)
                    <*> newName (printf "lyt%d'" i)


-- | Generate the recursive call to 'execNested'
-- 'lyt<n>' <- execNested conn lyt<n> ty<n>'
mkExecNestedStmt :: Name -> Name -> Name -> Stmt
mkExecNestedStmt tyName lytName resLytName =
    let execNested = VarE $ mkName "execNested"
        conn       = VarE $ mkName "conn"
        callE      = AppE (AppE (AppE execNested conn) (VarE lytName)) (VarE tyName)

    in BindS (VarP resLytName) callE

-- | Generate the case for one particular tuple type
mkExecTupleMatch :: Int -> Q Match
mkExecTupleMatch width = do
    tyNames               <- mapM elemTyName [1..width]
    (lytNames, lytNames') <- unzip <$> mapM elemLytName [1..width]

    -- '([lyt1, ..., lyt<n>], Tuple<n>T ty1 ... ty<n>)'
    let pat = TupP [ ListP $ map VarP lytNames
                   , ConP (tupTyConstName "F" width) (map VarP tyNames)
                   ]

    -- 'return $ TTuple $ TTuple<n> ty lyt1 ... lyt<n>'
    let execNestedStmts = zipWith3 mkExecNestedStmt tyNames lytNames lytNames'
        returnStmt      = NoBindS $ AppE (VarE 'return)
                                  $ AppE (ConE $ mkName "TTuple")
                                  $ foldl' AppE
                                           (AppE (ConE $ tabTupleConsName width) (VarE $ mkName "ty"))
                                           (map VarE lytNames')


    return $ Match pat (NormalB $ DoE $ execNestedStmts ++ [returnStmt]) []

-- | Generate a lambda expression that matches on a tuple type layout
-- and recursively calls execNested on the tuple member layouts.
-- @
-- \lyts ty ->
--  case (lyts, ty) of
--      ([lyt1, ..., lyt<n>], Tuple<n>T ty1 ... ty<n>) -> do
--          lyt1' <- execNested conn lyt1 ty1
--          ...
--          lyt<n>' <- execNested conn lyt<n> ty<n>
--          return $ TTuple $ TTuple<n> ty lyt1 ... lyt<n>
-- @
--
-- The lambda expression is /not/ closed: The names 'conn' and 'ty' must be in
-- scope where 'conn' is the database connection and 'ty' is the tuple type being
-- dissected.
mkExecTuple :: Int -> Q Exp
mkExecTuple maxWidth = do
    lytName       <- newName "lyts"
    tyName        <- newName "tys"

    tupMatches    <- mapM mkExecTupleMatch [2..maxWidth]
    impossibleExp <- impossible
    let matches = tupMatches ++ [Match WildP (NormalB impossibleExp) []]

    let lamBody = CaseE (TupE [VarE lytName, VarE tyName]) matches
    return $ LamE [VarP lytName, VarP tyName] lamBody

--------------------------------------------------------------------------------
-- Generate tuple layout type containing individual query results or
-- segmaps. The code generated for both is mostly identical except for
-- the layout type constructor and the constructor names.

tupElemTyName :: Int -> Q Name
tupElemTyName i = newName $ printf "t%d" i

-- | Generate a single constructor for the 'TabTuple' type.
mkTupleLytCons :: Name -> (Type -> Type) -> (Int -> Name) -> Int -> Q Con
mkTupleLytCons tupTyName lytTyCons conName width = do

    tupElemTyNames <- mapM tupElemTyName [1..width]

    let tyVarBinders     = map PlainTV tupElemTyNames

        -- (t1, ..., t<n>)
        tupTy            = foldl' AppT (TupleT width)
                           $ map VarT tupElemTyNames

        -- a ~ (t1, ..., t<n>)
        tupConstraint    = equalConstrTy (VarT tupTyName) tupTy

        -- Reify t1, ..., Reify t<n>
        reifyConstraints = map (\n -> nameTyApp ''DSH.Reify (VarT n)) tupElemTyNames

        constraints      = tupConstraint : reifyConstraints

    let -- 'Type a'
        dshTypeTy  = (NotStrict, AppT (ConT ''DSH.Type) (VarT tupTyName))
        -- 'TabLayout t1, TabLayout t<n>
        elemLytTys = [ (NotStrict, lytTyCons (VarT t)) -- AppT (ConT $ mkName "TabLayout") (VarT t))
                     | t <- tupElemTyNames
                     ]
        argTys     = dshTypeTy : elemLytTys

    return $ ForallC tyVarBinders constraints
           $ NormalC (conName width) {- (tabTupleConsName width) -} argTys

-- | Generate the data type for 'TabTuple'/'SegTuple' layouts that contain
-- tabular query results.
-- @
-- data TabTuple a where
--     TTuple3 :: (Reify t1, ..., Reify t<n>) => Type (t1, ..., t<n>)
--                                            -> TabLayout t1
--                                            -> ...
--                                            -> TabLayout t<n>
--                                            -> TabTuple (t1, ..., t<n>)
-- @
--
-- Because TH does not directly support GADT syntax, we have to
-- emulate it using explicit universal quantification:
--
-- @
-- data TabTuple a =
--     forall t1, ..., t<n>. a ~ (t1, ..., t<n>),
--                           Reify t1,
--                           ...
--                           Reify t<n> =>
--                           Type a -> TabLayout t1 -> ... -> TabLayout t<n>
-- @
mkTupleLyt :: Name -> (Type -> Type) -> (Int -> Name) -> Int -> Q [Dec]
mkTupleLyt tyName lytTyCons conName maxWidth = do
    tupTyName <- newName "a"
    cons      <- mapM (mkTupleLytCons tupTyName lytTyCons conName) [2..maxWidth]

    return $ [DataD [] tyName  [PlainTV tupTyName] cons []]

--------------------------------------------------------------------------------
-- Generate the tuple layout type containing tabular results

mkTabTupleType :: Int -> Q [Dec]
mkTabTupleType maxWidth = mkTupleLyt tabTupleTyName tabLayoutTyCons tabTupleConsName maxWidth
  where
    tabLayoutTyCons :: Type -> Type
    tabLayoutTyCons argTy = AppT (ConT $ mkName "TabLayout") argTy

    tabTupleTyName :: Name
    tabTupleTyName = mkName "TabTuple"

--------------------------------------------------------------------------------
-- Generate the tuple layout type containing segment maps

mkSegTupleType :: Int -> Q [Dec]
mkSegTupleType maxWidth = mkTupleLyt segTupleTyName segLayoutTyCons segTupleConsName maxWidth
  where
    segLayoutTyCons :: Type -> Type
    segLayoutTyCons argTy = AppT (ConT $ mkName "SegLayout") argTy

    segTupleTyName :: Name
    segTupleTyName = mkName "SegTuple"

--------------------------------------------------------------------------------
-- Generate the mapping function between tabular and segment map layouts.

mkSegmentTupleMatch :: Int -> Q Match
mkSegmentTupleMatch width = do
    tyName   <- newName "ty"
    lytNames <- mapM (\i -> newName $ printf "tlyt%d" i) [1..width]
    let tuplePat = ConP (tabTupleConsName width) (VarP tyName : map VarP lytNames)

    let segFun  = VarE $ mkName "segmentLayout"
        segLyts = map (\l -> AppE segFun (VarE l)) lytNames

    let bodyExp = foldl' AppE (ConE $ segTupleConsName width)
                              (VarE tyName : segLyts)
    return $ Match tuplePat (NormalB bodyExp) []

-- | Generate the definition for the 'segmentTuple' function that maps
-- layouts with tabular SQL results to layouts with segment maps.
-- @
--
-- \keyCols lyt ->
--   case lyt of
--   ...
--   (TTuple<n> ty tlyt1 ... tlyt<n>) = STuple<n> ty (segmentLayout keyCols tlyt1)
--                                                   ...
--                                                   (segmentLayout keyCols tlyt<n>)
-- @
mkSegmentTupleFun :: Int -> Q Exp
mkSegmentTupleFun maxWidth = do
    lytName       <- newName "lyt"
    tupMatches    <- mapM mkSegmentTupleMatch [2..maxWidth]
    let lamBody = CaseE (TupE [VarE lytName]) tupMatches

    return $ LamE [VarP lytName] lamBody

--------------------------------------------------------------------------------
-- Generate the constructor function from a segmap tuple layout to a
-- tuple value

mkConstructTupleMatch :: Name -> Name -> Int -> Q Match
mkConstructTupleMatch keysName rowName width = do
    lytNames <- mapM (\i -> newName $ printf "slyt%d" i) [1..width]

    let tuplePat = ConP (segTupleConsName width) (WildP : map VarP lytNames)

    let constructFun    = VarE $ mkName "constructVal"
        constructArgs l = [VarE keysName, VarE l, VarE rowName ]
        resultElemExps  = [ foldl' AppE constructFun (constructArgs l)
                          | l <- lytNames
                          ]
        tupleConstE     = ConE $ innerConst "F" width
        resultValExp    = AppE (ConE $ mkName "F.TupleConstE")
                               (foldl' AppE tupleConstE resultElemExps)

    return $ Match tuplePat (NormalB resultValExp) []

mkConstructTuple :: Int -> Q Exp
mkConstructTuple maxWidth = do
    keysName      <- newName "keyCols"
    lytName       <- newName "lyt"
    rowName       <- newName "row"

    tupMatches    <- mapM (mkConstructTupleMatch keysName rowName)
                          [2..maxWidth]
    let lamBody = CaseE (TupE [VarE lytName]) tupMatches

    return $ LamE [VarP keysName, VarP lytName, VarP rowName] lamBody