dsh-sql-0.2.0.0: src/Database/DSH/Backend/Sql/Opt/Properties/FD.hs
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Infer functional dependencies from table algebra operators.
module Database.DSH.Backend.Sql.Opt.Properties.FD
( inferFDNullOp
, inferFDUnOp
, inferFDBinOp
) where
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set.Monad as S
import Data.Tuple
import Database.Algebra.Table.Lang
import Database.DSH.Backend.Sql.Opt.Properties.Auxiliary
import Database.DSH.Backend.Sql.Opt.Properties.Types
import Database.DSH.Common.Impossible
inferFDNullOp :: S.Set TypedAttr -> S.Set PKey -> NullOp -> FDSet
inferFDNullOp tcs ks op =
case op of
LitTable _ -> FDSet $ foldr (\k m -> M.insert k (cs S.\\ k) m) M.empty ks
TableRef _ -> FDSet $ foldr (\k m -> M.insert k (cs S.\\ k) m) M.empty ks
where
cs = fmap fst tcs
-- | Update an attribute set with new names. All attributes must find
-- a new name.
updateSetAll :: [(Attr, Attr)] -> S.Set Attr -> Maybe (S.Set Attr)
updateSetAll colMap cs =
S.foldr' (\c mcs -> S.insert <$> lookup c colMap <*> mcs)
(Just S.empty)
cs
-- | Update an attribute set with new names. Attributes for which no
-- new name exists are removed.
updateSet :: [(Attr, Attr)] -> S.Set Attr -> S.Set Attr
updateSet colMap cs = unionss $ fmap (\c -> maybe S.empty ss $ lookup c colMap) cs
updateFD :: [(Attr, Attr)] -> S.Set Attr -> S.Set Attr -> FDSet -> FDSet
updateFD colMap dets deps (FDSet m) =
case (updateSetAll colMap dets, updateSet colMap deps) of
(Just dets', deps') | deps' /= S.empty -> FDSet $ M.insert dets' deps' m
_ -> FDSet m
-- | Update a set of functional dependencies with new names from a
-- projection. A functional dependency is kept if all attributes from
-- the determinant set can be mapped and if at least one attribute
-- from the dependent set can be mapped.
updateFDSet :: [(Attr, Attr)] -> FDSet -> FDSet
updateFDSet colMap (FDSet m) = M.foldrWithKey (updateFD colMap) emptyFDSet m
-- -- | Add a functional dependency for a single attribute to a set of FDs.
-- addFunDep :: S.Set Attr -> Attr -> FDSet -> FDSet
-- addFunDep cs c (FDSet m) = FDSet $ M.insertWith S.union cs (ss c) m
-- | Add a dependency for a set of attributes to a set of FDs.
addFunDeps :: S.Set Attr -> S.Set Attr -> FDSet -> FDSet
addFunDeps cs cs' (FDSet m) = FDSet $ M.insertWith S.union cs cs' m
cols :: BottomUpProps -> S.Set Attr
cols p = fmap fst $ pCols p
inferFDUnOp :: BottomUpProps -> UnOp -> FDSet
inferFDUnOp p op =
case op of
Distinct _ -> pFunDeps p
Select _ -> pFunDeps p
RowNum (r, _, []) -> addFunDeps (ss r) (cols p) (pFunDeps p)
-- FIXME the combination of sorting and grouping cols propably
-- determines r.
RowNum (_, _, _) -> pFunDeps p
-- FIXME dependency r -> sortcols
RowRank _ -> pFunDeps p
Aggr (_, []) -> emptyFDSet
-- Dependencies among grouping columns stay intact and are
-- updated in the same way as for projections.
Aggr (as, gs) ->
let colMap = S.toList $ S.map swap $ S.fromList $ mapMaybe mapCol gs
in addFunDeps (ls $ map fst gs)
(S.fromList $ map snd as)
(updateFDSet colMap (pFunDeps p))
Project ps ->
let colMap = S.toList $ S.map swap $ S.fromList $ mapMaybe mapCol ps
in updateFDSet colMap (pFunDeps p)
Serialize _ -> pFunDeps p
WinFun _ -> $unimplemented
Rank _ -> $unimplemented
inferFDBinOp :: BottomUpProps -- ^ Properties of the left child
-> BottomUpProps -- ^ Properties of the right child
-> S.Set PKey -- ^ The keys of the operator itself
-> S.Set TypedAttr -- ^ The cols of the operator itself
-> BinOp -- ^ The operator
-> FDSet
inferFDBinOp p1 p2 ks cs op =
case op of
-- Determine functional dependency of a cartesian
-- product. Note: As we know that attribute sets of left and
-- right inputs are disjunct, we don't have to care for
-- collisions in the functional dependencies during unioning.
Cross _ -> FDSet $
-- Dependencies from either side are still valid after the product
(fdsRep $ pFunDeps p1)
`M.union`
(fdsRep $ pFunDeps p2)
`M.union`
-- The new combined keys determine all result columns of the product.
(foldr (\k m -> M.insert k ((fmap fst cs) S.\\ k) m) M.empty ks)
ThetaJoin _ -> FDSet $
(fdsRep $ pFunDeps p1)
`M.union`
(fdsRep $ pFunDeps p2)
`M.union`
(foldr (\k m -> M.insert k ((fmap fst cs) S.\\ k) m) M.empty ks)
SemiJoin _ -> pFunDeps p1
AntiJoin _ -> pFunDeps p1
LeftOuterJoin _ -> pFunDeps p1
DisjUnion _ -> emptyFDSet
Difference _ -> pFunDeps p1
EqJoin _ -> $unimplemented