dsh-sql-0.2.0.0: src/Database/DSH/Backend/Sql/Opt/Rewrite/Basic.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Database.DSH.Backend.Sql.Opt.Rewrite.Basic where
import Debug.Trace
import Text.Printf
import Control.Monad
import Data.Either
-- import Data.Either.Combinators
import Data.List hiding
(insert)
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set.Monad as S
import Database.Algebra.Dag.Common
import Database.Algebra.Table.Lang hiding
(replace)
import Database.DSH.Backend.Sql.Opt.Properties.Auxiliary
import Database.DSH.Backend.Sql.Opt.Properties.Const
import Database.DSH.Backend.Sql.Opt.Properties.Types
import Database.DSH.Backend.Sql.Opt.Rewrite.Common
import Database.DSH.Common.Impossible
import Database.DSH.Common.Opt
cleanup :: TARewrite Bool
cleanup = iteratively $ sequenceRewrites [ applyToAll noProps cleanupRules
, applyToAll inferAll cleanupRulesTopDown
]
cleanupRules :: TARuleSet ()
cleanupRules = [ stackedProject
-- , serializeProject
, pullProjectWinFun
, pullProjectSelect
, pullProjectSerialize
, pullProjectRownum
, pullProjectAggr
, pullProjectSemiJoinLeft
, pullProjectSemiJoinRight
, inlineProjectAggr
, duplicateSortingCriteriaWin
, duplicateSortingCriteriaRownum
-- , duplicateSortingCriteriaSerialize
, bypassRownumProject
, pruneSerializeSortCols
]
cleanupRulesTopDown :: TARuleSet AllProps
cleanupRulesTopDown = [ unreferencedBaseTableCols
, unreferencedRownum
, unreferencedRank
, unreferencedProjectCols
, unreferencedAggrCols
, unreferencedLiteralCols
, unreferencedGroupingCols
, pruneSerializeSortColsFD
, inlineSortColsRownum
-- , inlineSortColsSerialize
, inlineSortColsWinFun
, keyPrefixOrdering
, constAggrKey
, constRownumCol
, constRowRankCol
-- , constSerializeCol
, constWinOrderCol
, pullProjectThetaJoinLeft
, pullProjectThetaJoinRight
, pullProjectCrossLeft
, pullProjectCrossRight
]
----------------------------------------------------------------------------------
-- Rewrite rules
---------------------------------------------------------------------------
-- ICols rewrites
unreferencedBaseTableCols :: TARule AllProps
unreferencedBaseTableCols q =
$(dagPatMatch 'q "TableRef args "
[| do
let (n, schema, keys) = $(v "args")
reqCols <- pICols <$> td <$> properties q
let schema' = filter (\(c, _) -> S.member c reqCols) schema
predicate $ length schema' < length schema
return $ do
logRewrite "Basic.ICols.Table" q
let keys' = filter (\(Key k) -> all (\c -> S.member c reqCols) k)
keys
void $ replaceWithNew q $ NullaryOp $ TableRef (n, schema', keys') |])
-- | Prune a rownumber operator if its output is not required
unreferencedRownum :: TARule AllProps
unreferencedRownum q =
$(dagPatMatch 'q "RowNum args (q1)"
[| do
(res, _, _) <- return $(v "args")
neededCols <- pICols <$> td <$> properties q
predicate $ not (res `S.member` neededCols)
return $ do
logRewrite "Basic.ICols.Rownum" q
replace q $(v "q1") |])
-- | Prune a rownumber operator if its output is not required
unreferencedRank :: TARule AllProps
unreferencedRank q =
$(dagPatMatch 'q "[Rank | RowRank] args (q1)"
[| do
(res, _) <- return $(v "args")
neededCols <- pICols <$> td <$> properties q
predicate $ not (res `S.member` neededCols)
return $ do
logRewrite "Basic.ICols.Rank" q
replace q $(v "q1") |])
-- | Prune projections from a project operator if the result columns
-- are not required.
unreferencedProjectCols :: TARule AllProps
unreferencedProjectCols q =
$(dagPatMatch 'q "Project projs (q1)"
[| do
neededCols <- pICols <$> td <$> properties q
let neededProjs = filter (flip S.member neededCols . fst) $(v "projs")
-- Only modify the project if we could actually get rid of some columns.
predicate $ length neededProjs < length $(v "projs")
return $ do
logRewrite "Basic.ICols.Project" q
void $ replaceWithNew q $ UnOp (Project neededProjs) $(v "q1") |])
-- | Remove aggregate functions whose output is not referenced.
unreferencedAggrCols :: TARule AllProps
unreferencedAggrCols q =
$(dagPatMatch 'q "Aggr args (q1)"
[| do
neededCols <- pICols <$> td <$> properties q
(aggrs, partCols) <- return $(v "args")
let neededAggrs = filter (flip S.member neededCols . snd) aggrs
predicate $ length neededAggrs < length aggrs
return $ do
case neededAggrs of
-- If the output of all aggregate functions is not
-- required, we can replace it with a distinct operator
-- on the grouping columns.
[] -> do
logRewrite "Basic.ICols.Aggr.PruneAggr" q
projectNode <- insert $ UnOp (Project partCols) $(v "q1")
void $ replaceWithNew q $ UnOp (Distinct ()) projectNode
-- Otherwise, we just prune the unreferenced aggregate functions
_ : _ -> do
logRewrite "Basic.ICols.Aggr.Narrow" q
void $ replaceWithNew q $ UnOp (Aggr (neededAggrs, partCols)) $(v "q1") |])
unreferencedLiteralCols :: TARule AllProps
unreferencedLiteralCols q =
$(dagPatMatch 'q "LitTable tab "
[| do
neededCols <- pICols <$> td <$> properties q
predicate (not $ S.null neededCols)
let (tuples, schema) = $(v "tab")
predicate (not $ null tuples)
predicate $ S.size neededCols < length schema
return $ do
let columns = transpose tuples
let (reqCols, reqSchema) =
unzip
$ filter (\(_, (colName, _)) -> colName `S.member` neededCols)
$ zip columns schema
let reqTuples = transpose reqCols
void $ replaceWithNew q $ NullaryOp $ LitTable (reqTuples, reqSchema) |])
--------------------------------------------------------------------------------
-- Rewrites based on functional dependencies
-- | Helper function for 'prunePartExprs': Consider one particular not
-- required column and check wether it is functionally determined by
-- required columns and some other not required columns.
prunePartCols :: [(PartAttr, Attr)] -- ^ Columns to consider for removal
-> FDSet
-> [(PartAttr, Attr)] -- ^ Columns that will be preserved
-> S.Set Attr -- ^ Required columns
-> S.Set (S.Set Attr) -- ^ All determinant sets to consider
-> [(PartAttr, Attr)]
prunePartCols [] _ reqProj _ _ = reqProj
prunePartCols ((c, gc) : ts) fds reqProj reqCols dets =
case find (\ds -> coveredCol fds gc ds) dets' of
-- 'det' determines 'gc' -> remove 'gc'
Just det ->
let -- Columns that are not required downstream but that
-- are part of the determinant set that determines gc
-- and need to be preserved.
unreqDetCols = S.intersection det otherUnreqCols
-- remove all unrequired columns that are in the
-- determinant set from the set of columns to consider
-- for removal
(keepProjs, ts') = partition (\dc -> snd dc `S.member` unreqDetCols)
ts
-- if '(c, gc)' can be removed, all other (not
-- required) projections '(c', gc)' can be removed as
-- well.
ts'' = filter ((/= gc) . snd) ts'
-- Preserve all columns that are part of the
-- determinant set just used.
nextReqProjs = keepProjs ++ reqProj
-- The set of columns that we keep in any case,
-- including the columns in 'det'.
nextReqCols = (unreqDetCols ∪ reqCols)
-- Remove all determinant sets that contain the column
-- we just removed.
nextDets = S.filter (\ds -> not $ gc `S.member` ds) dets
in prunePartCols ts'' fds nextReqProjs nextReqCols nextDets
-- 'gc' is not determined by any remaining determinant set.
Nothing ->
let nextReqProjs = (c, gc) : reqProj
nextReqCols = S.insert gc reqCols
-- If gc is required, we can still remove all other
-- occurences of gc: For grouping semantics, one
-- occurence is enough. Furthermore, we know that all
-- grouping projections in 'ts' are /not/ required
-- from above (icols).
ts' = filter ((/= gc) . snd) ts
in prunePartCols ts' fds nextReqProjs nextReqCols dets
where
otherUnreqCols = S.fromList $ map snd ts
candCols = reqCols ∪ otherUnreqCols
dets' = S.filter (\ds -> ds `S.isSubsetOf` candCols) dets
-- | Prune not required grouping columns that are functionally
-- determined by a set of other grouping columns.
--
-- The key to efficiently check wether a column is determined by a set
-- of columns is not to consider some subsets of the columns that
-- /might/ form a determinant set. Instead, we check exactly those
-- subsets that occur as determinant sets in the set of functional
-- dependencies.
--
-- This is a heuristic optimization and does not result in the exact
-- optimum: Computing the minimum set of non-required columns such
-- that the grouping is equivalent to the original grouping seems to
-- be considerably harder.
prunePartExprs :: S.Set Attr -- ^ Columns required from above
-> [(PartAttr, Expr)] -- ^ Grouping projections
-> FDSet -- ^ All available FDs
-> [(PartAttr, Expr)]
prunePartExprs icols groupProjs fds =
-- trace ("PRUNEPARTEXPRS REQPARTCOLS " ++ show reqPartCols) $
-- trace ("PRUNEPARTEXPRS NOTREQPARTCOLS " ++ show notReqPartCols) $
-- trace ("PRUNEPARTEXPRS DETS " ++ showSet (showSet id) dets) $
partExprs
++ map mkExp (reqPartCols)
++ map mkExp (prunePartCols notReqPartCols' fds [] reqCols dets)
where
dets = S.filter (\ds -> ds `S.isSubsetOf` allCols)
$ S.fromList $ M.keys $ fdsRep fds
f :: (PartAttr, Expr) -> Either (PartAttr, Expr) (PartAttr, Attr)
f (c, ColE gc) = Right (c, gc)
f (c, e) = Left (c, e)
mkExp :: (PartAttr, Attr) -> (PartAttr, Expr)
mkExp (c, gc) = (c, ColE gc)
(partExprs, partCols) = partitionEithers $ map f groupProjs
(reqPartCols, notReqPartCols) = partition (\gp -> fst gp `S.member` icols)
partCols
-- Seed the set of required grouping columns with those who are
-- required from above.
reqCols = S.fromList $ map snd reqPartCols
-- Before considering non-trivial functional dependencies, we try
-- to remove grouping columns based on trivial functional
-- dependencies: If the projection column of a grouping projection
-- is not required from above and the corresponding grouping
-- column is already present in the set of required grouping
-- columns, the projection can be safely removed.
notReqPartCols' = filter (\(_, gc) -> not $ gc `S.member` reqCols)
notReqPartCols
allCols = S.fromList $ map snd partCols
-- | Determine wether a column c is functionally determined by a
-- set of columns.
coveredCol :: FDSet -> Attr -> S.Set Attr -> Bool
coveredCol fds c cs =
case M.lookup cs (fdsRep fds) of
Just deps -> c `S.member` deps
Nothing -> False
triviallyCovered :: S.Set Attr -> Attr -> Bool
triviallyCovered cs c = c `S.member` cs
-- | Prune unreferenced grouping columns based on functional
-- dependencies.
unreferencedGroupingCols :: TARule AllProps
unreferencedGroupingCols q =
$(dagPatMatch 'q "Aggr args (q1)"
[| do
neededCols <- pICols <$> td <$> properties q
fds <- pFunDeps <$> bu <$> properties $(v "q1")
(aggrs, partCols) <- return $(v "args")
-- trace ("AGGR PARTCOLS " ++ show partCols) $ return ()
-- trace ("AGGR ICOLS " ++ show neededCols) $ return ()
-- trace ("AGGR FDS " ++ show fds) $ return ()
predicate $ not $ S.null $ (S.fromList $ map fst partCols) S.\\ neededCols
predicate $ length partCols > 1
let partCols' = prunePartExprs neededCols partCols fds
predicate $ length partCols' < length partCols
-- trace ("AGGR GROUP " ++ show partCols'') $ return ()
return $ do
logRewrite "Basic.ICols.Aggr.PruneGroupingCols" q
void $ replaceWithNew q $ UnOp (Aggr (aggrs, partCols')) $(v "q1") |])
--------------------------------------------------------------------------------
-- | Prune ordering columns that are functionally determined by
-- preceding columns.
pruneOrdColsFD :: FDSet -> [OrdCol] -> [OrdCol]
pruneOrdColsFD fds ordCols = go S.empty ordCols
where
go :: S.Set Attr -> [OrdCol] -> [OrdCol]
go cs (OrdCol c@(_, d) (ColE oc) : ocs)
| any (\ds -> coveredCol fds oc ds) dets
= go cs ocs
| otherwise
= OrdCol c (ColE oc) : go (S.insert oc cs) ocs
where
dets = S.filter (\ds -> ds `S.isSubsetOf` cs)
$ S.fromList $ M.keys $ fdsRep fds
go cs (OrdCol c e : ocs) = OrdCol c e : go cs ocs
go _ [] = []
isAscOrd :: OrdCol -> Bool
isAscOrd (OrdCol (_, Asc) _) = True
isAscOrd _ = False
-- | Prune ordering columns based on functional dependenices.
pruneSerializeSortColsFD :: TARule AllProps
pruneSerializeSortColsFD q =
$(dagPatMatch 'q "Serialize args (q1)"
[| do
fds <- pFunDeps <$> bu <$> properties $(v "q1")
(rcs, kcs, ocs, pcs) <- return $(v "args")
-- trace ("SERIALIZE OCS " ++ show ocs) $ return ()
-- trace ("SERIALIZE FDS " ++ show fds) $ return ()
-- We restrict pruning to all-ascending orders for a simple
-- reason: We have no clue what should happen if there are
-- descending orders as well.
predicate $ all isAscOrd ocs
let ocs' = pruneOrdColsFD fds ocs
predicate $ length ocs' < length ocs
return $ do
logRewrite "Basic.ICols.Serialize.PruneSortingCols.FD" q
let args' = (rcs, kcs, ocs', pcs)
void $ replaceWithNew q $ UnOp (Serialize args') $(v "q1") |])
--------------------------------------------------------------------------------
-- | Prune ordering expressions that occur more than once
pruneOrdCols :: [OrdCol] -> [OrdCol]
pruneOrdCols ordCols = go S.empty ordCols
where
go :: S.Set Expr -> [OrdCol] -> [OrdCol]
go es (OrdCol c e : ocs)
| S.member e es
= go es ocs
| otherwise
= OrdCol c e : go (S.insert e es) ocs
go _ [] = []
-- | Eliminate ordering expressions that occur more than once.
pruneSerializeSortCols :: TARule ()
pruneSerializeSortCols q =
$(dagPatMatch 'q "Serialize args (q1)"
[| do
(rcs, kcs, ocs, pcs) <- return $(v "args")
-- trace ("SERIALIZE OCS " ++ show ocs) $ return ()
-- trace ("SERIALIZE FDS " ++ show fds) $ return ()
-- We restrict pruning to all-ascending orders for a simple
-- reason: We have no clue what should happen if there are
-- descending orders as well.
predicate $ all isAscOrd ocs
let ocs' = pruneOrdCols ocs
predicate $ length ocs' < length ocs
return $ do
logRewrite "Basic.Serialize.PruneSortingCols" q
let args' = (rcs, kcs, ocs', pcs)
void $ replaceWithNew q $ UnOp (Serialize args') $(v "q1") |])
----------------------------------------------------------------------------------
-- Basic Const rewrites
isConstExpr :: [ConstCol] -> Expr -> Bool
isConstExpr constCols e = isJust $ constExpr constCols e
-- | Prune const columns from aggregation keys
constAggrKey :: TARule AllProps
constAggrKey q =
$(dagPatMatch 'q "Aggr args (q1)"
[| do
constCols <- pConst <$> bu <$> properties $(v "q1")
neededCols <- S.toList <$> pICols <$> td <$> properties q
(aggrFuns, keyCols@(_:_)) <- return $(v "args")
let keyCols' = filter (\(_, e) -> not $ isConstExpr constCols e) keyCols
prunedKeys = (map fst keyCols) \\ (map fst keyCols')
predicate $ not $ null prunedKeys
return $ do
logRewrite "Basic.Const.Aggr" q
let necessaryKeys = prunedKeys `intersect` neededCols
constProj c = lookup c constCols >>= \val -> return (c, ConstE val)
constProjs = mapMaybe constProj necessaryKeys
proj = map (\(_, c) -> (c, ColE c)) aggrFuns
++
map (\(c, _) -> (c, ColE c)) keyCols'
++
constProjs
aggrNode <- insert $ UnOp (Aggr ($(v "aggrFuns"), keyCols')) $(v "q1")
void $ replaceWithNew q $ UnOp (Project proj) aggrNode |])
constRownumCol :: TARule AllProps
constRownumCol q =
$(dagPatMatch 'q "RowNum args (q1)"
[| do
constCols <- pConst <$> bu <$> properties $(v "q1")
(resCol, sortCols, partExprs) <- return $(v "args")
let sortCols' = filter (\(e, _) -> not $ isConstExpr constCols e) sortCols
predicate $ length sortCols' < length sortCols
return $ do
logRewrite "Basic.Const.RowNum" q
void $ replaceWithNew q $ UnOp (RowNum (resCol, sortCols', partExprs)) $(v "q1") |])
constRowRankCol :: TARule AllProps
constRowRankCol q =
$(dagPatMatch 'q "RowRank args (q1)"
[| do
constCols <- pConst <$> bu <$> properties $(v "q1")
(resCol, sortCols) <- return $(v "args")
let sortCols' = filter (\(e, _) -> not $ isConstExpr constCols e) sortCols
predicate $ length sortCols' < length sortCols
return $ do
logRewrite "Basic.Const.RowRank" q
void $ replaceWithNew q $ UnOp (RowRank (resCol, sortCols')) $(v "q1") |])
-- constSerializeCol :: TARule AllProps
-- constSerializeCol q =
-- $(dagPatMatch 'q "Serialize args (q1)"
-- [| do
-- (mDescr, RelPos sortCols, payload) <- return $(v "args")
-- constCols <- map fst <$> pConst <$> bu <$> properties $(v "q1")
-- let sortCols' = filter (\c -> c `notElem` constCols) sortCols
-- predicate $ length sortCols' < length sortCols
-- return $ do
-- logRewrite "Basic.Const.Serialize" q
-- void $ replaceWithNew q $ UnOp (Serialize (mDescr, RelPos sortCols', payload)) $(v "q1") |])
constWinOrderCol :: TARule AllProps
constWinOrderCol q =
$(dagPatMatch 'q "WinFun args (q1)"
[| do
constCols <- pConst <$> bu <$> properties $(v "q1")
let (f, part, sortCols, frameSpec) = $(v "args")
let sortCols' = filter (\(e, _) -> not $ isConstExpr constCols e) sortCols
predicate $ length sortCols' < length sortCols
return $ do
logRewrite "Basic.Const.WinFun" q
void $ replaceWithNew q $ UnOp (WinFun (f, part, sortCols', frameSpec)) $(v "q1") |])
----------------------------------------------------------------------------------
-- Basic Order rewrites
-- | @lookupSortCol@ returns @Left@ if there is no mapping from the
-- original sort column and @Right@ if there is a mapping from the
-- original sort column to a list of columns that define the same
-- order.
lookupSortCol :: SortSpec -> Orders -> Either [SortSpec] [SortSpec]
lookupSortCol (ColE oldSortCol, Asc) os =
case lookup oldSortCol os of
Nothing -> Left [(ColE oldSortCol, Asc)]
Just newSortCols -> Right $ map (\c -> (ColE c, Asc)) newSortCols
-- We do not inline into arbitrary expressions for now. Likewise, we
-- do not consider non-ascending sorting.
lookupSortCol (e, dir) _ = Left [(e, dir)]
inlineSortColsRownum :: TARule AllProps
inlineSortColsRownum q =
$(dagPatMatch 'q "RowNum o (q1)"
[| do
(resCol, sortCols@(_:_), groupCols) <- return $(v "o")
predicate $ all ((== Asc) . snd) sortCols
orders@(_:_) <- pOrder <$> bu <$> properties $(v "q1")
-- For each sorting column, try to find the original
-- order-defining sorting columns.
let mSortCols = map (flip lookupSortCol orders) sortCols
-- The rewrite should only fire if something actually changes
predicate $ any isRight mSortCols
let sortCols' = nub $ concatMap (either id id) mSortCols
return $ do
logRewrite "Basic.InlineOrder.RowNum" q
void $ replaceWithNew q $ UnOp (RowNum (resCol, sortCols', groupCols)) $(v "q1") |])
-- inlineSortColsSerialize :: TARule AllProps
-- inlineSortColsSerialize q =
-- $(dagPatMatch 'q "Serialize scols (q1)"
-- [| do
-- (d, RelPos cs, reqCols) <- return $(v "scols")
-- orders@(_:_) <- pOrder <$> bu <$> properties $(v "q1")
-- let cs' = nub $ concatMap (\c -> maybe [c] id $ lookup c orders) cs
-- predicate $ cs /= cs'
-- return $ do
-- logRewrite "Basic.InlineOrder.Serialize" q
-- void $ replaceWithNew q $ UnOp (Serialize (d, RelPos cs', reqCols)) $(v "q1") |])
inlineSortColsWinFun :: TARule AllProps
inlineSortColsWinFun q =
$(dagPatMatch 'q "WinFun args (q1)"
[| do
let (f, part, sortCols, frameSpec) = $(v "args")
orders@(_:_) <- pOrder <$> bu <$> properties $(v "q1")
-- For each sorting column, try to find the original
-- order-defining sorting columns.
let mSortCols = map (flip lookupSortCol orders) sortCols
-- The rewrite should only fire if something actually changes
predicate $ any isRight mSortCols
let sortCols' = nub $ concatMap (either id id) mSortCols
args' = (f, part, sortCols', frameSpec)
return $ do
logRewrite "Basic.InlineOrder.WinFun" q
void $ replaceWithNew q $ UnOp (WinFun args') $(v "q1") |])
isKeyPrefix :: S.Set PKey -> [SortSpec] -> Bool
isKeyPrefix keys orderCols =
case mapM mColE $ map fst orderCols of
Just cols -> S.fromList cols `S.member` keys
Nothing -> False
-- | If a prefix of the ordering columns in a rownum operator forms a
-- key, the suffix can be removed.
keyPrefixOrdering :: TARule AllProps
keyPrefixOrdering q =
$(dagPatMatch 'q "RowNum args (q1)"
[| do
(resCol, sortCols, []) <- return $(v "args")
keys <- pKeys <$> bu <$> properties $(v "q1")
predicate $ not $ null sortCols
-- All non-empty and incomplete prefixes of the ordering
-- columns
let ordPrefixes = init $ drop 1 (inits sortCols)
Just prefix <- return $ find (isKeyPrefix keys) ordPrefixes
return $ do
logRewrite "Basic.SimplifyOrder.KeyPrefix" q
let sortCols' = take (length prefix) sortCols
void $ replaceWithNew q $ UnOp (RowNum (resCol, sortCols', [])) $(v "q1") |])
duplicateSortingCriteriaRownum :: TARule ()
duplicateSortingCriteriaRownum q =
$(dagPatMatch 'q "RowNum args (q1)"
[| do
(resCol, sortCols, []) <- return $(v "args")
let sortCols' = nub sortCols
predicate $ length sortCols' < length sortCols
return $ do
logRewrite "Basic.SimplifyOrder.Duplicates.Rownum" q
let args' = (resCol, sortCols', [])
void $ replaceWithNew q $ UnOp (RowNum args') $(v "q1") |])
duplicateSortingCriteriaWin :: TARule ()
duplicateSortingCriteriaWin q =
$(dagPatMatch 'q "WinFun args (q1)"
[| do
let (winFuns, part, sortCols, mFrameBounds) = $(v "args")
let sortCols' = nub sortCols
predicate $ length sortCols' < length sortCols
return $ do
logRewrite "Basic.SimplifyOrder.Duplicates.WinFun" q
let args' = (winFuns, part, sortCols', mFrameBounds)
void $ replaceWithNew q $ UnOp (WinFun args') $(v "q1") |])
-- duplicateSortingCriteriaSerialize :: TARule ()
-- duplicateSortingCriteriaSerialize q =
-- $(dagPatMatch 'q "Serialize args (q1)"
-- [| do
-- (mDescr, RelPos sortCols, payload) <- return $(v "args")
-- let sortCols' = nub sortCols
-- predicate $ length sortCols' < length sortCols
-- return $ do
-- logRewrite "Basic.SimplifyOrder.Duplicates.Serialize" q
-- let args' = (mDescr, RelPos sortCols', payload)
-- void $ replaceWithNew q $ UnOp (Serialize args') $(v "q1") |])
-- | If a rownum output is not refererenced by a parent projection,
-- discard it. This handles the case of a multi-parent rownum that is
-- not required by a specific parent but is required by other parents
-- and therefore can't be eliminated globally.
--
-- FIXME It would be more elegant and general to make the ICols
-- analysis parent-aware so that we can tell for an operator wether it
-- is required by a specific parent.
bypassRownumProject :: TARule ()
bypassRownumProject q =
$(dagPatMatch 'q "Project p (RowNum s (q1))"
[| do
let (resCol, _, _) = $(v "s")
predicate $ not $ S.member resCol (S.unions $ map (exprCols . snd) $(v "p"))
return $ do
logRewrite "Basic.SimplifyOrder.BypassRownum" q
void $ replaceWithNew q $ UnOp (Project $(v "p")) $(v "q1") |])
----------------------------------------------------------------------------------
-- Serialize rewrites
-- -- | Merge a projection which only maps columns into a Serialize operator.
-- serializeProject :: TARule ()
-- serializeProject q =
-- $(dagPatMatch 'q "Serialize scols (Project projs (q1))"
-- [| do
-- (d, p, reqCols) <- return $(v "scols")
-- let projCol (c', ColE c) = return (c', c)
-- projCol _ = fail "no match"
-- lookupFail x xys = case lookup x xys of
-- Just y -> return y
-- Nothing -> fail "no match"
-- colMap <- mapM projCol $(v "projs")
-- -- find new names for all required columns
-- reqCols' <- mapM (\(PayloadCol c) -> PayloadCol <$> lookupFail c colMap) reqCols
-- -- find new name for the descriptor column (if required)
-- d' <- case d of
-- Just (DescrCol c) -> Just <$> DescrCol <$> lookupFail c colMap
-- Nothing -> return Nothing
-- -- find new name for the pos column (if required)
-- p' <- case p of
-- AbsPos c -> AbsPos <$> lookupFail c colMap
-- RelPos cs -> RelPos <$> mapM (flip lookupFail colMap) cs
-- NoPos -> return NoPos
-- return $ do
-- logRewrite "Basic.Serialize.Project" q
-- void $ replaceWithNew q $ UnOp (Serialize (d', p', reqCols')) $(v "q1") |])
--------------------------------------------------------------------------------
-- Pulling projections through other operators and merging them into
-- other operators
inlineExpr :: [Proj] -> Expr -> Expr
inlineExpr proj expr =
case expr of
BinAppE op e1 e2 -> BinAppE op (inlineExpr proj e1) (inlineExpr proj e2)
UnAppE op e -> UnAppE op (inlineExpr proj e)
ColE c -> fromMaybe (failedLookup c) (lookup c proj)
ConstE val -> ConstE val
IfE c t e -> IfE (inlineExpr proj c) (inlineExpr proj t) (inlineExpr proj e)
where
failedLookup :: Attr -> a
failedLookup c = trace (printf "mergeProjections: column lookup %s failed\n%s\n%s"
c (show expr) (show proj))
$impossible
mergeProjections :: [Proj] -> [Proj] -> [Proj]
mergeProjections proj1 proj2 = map (\(c, e) -> (c, inlineExpr proj2 e)) proj1
stackedProject :: TARule ()
stackedProject q =
$(dagPatMatch 'q "Project ps1 (Project ps2 (qi))"
[| do
return $ do
let ps = mergeProjections $(v "ps1") $(v "ps2")
logRewrite "Basic.Project.Merge" q
void $ replaceWithNew q $ UnOp (Project ps) $(v "qi") |])
mapWinFun :: (Expr -> Expr) -> WinFun -> WinFun
mapWinFun f (WinMax e) = WinMax $ f e
mapWinFun f (WinMin e) = WinMin $ f e
mapWinFun f (WinSum e) = WinSum $ f e
mapWinFun f (WinAvg e) = WinAvg $ f e
mapWinFun f (WinAll e) = WinAll $ f e
mapWinFun f (WinAny e) = WinAny $ f e
mapWinFun f (WinFirstValue e) = WinFirstValue $ f e
mapWinFun f (WinLastValue e) = WinLastValue $ f e
mapWinFun _ WinCount = WinCount
mapAggrFun :: (Expr -> Expr) -> AggrType -> AggrType
mapAggrFun f (Max e) = Max $ f e
mapAggrFun f (Min e) = Min $ f e
mapAggrFun f (Sum e) = Sum $ f e
mapAggrFun f (Avg e) = Avg $ f e
mapAggrFun f (All e) = All $ f e
mapAggrFun f (Any e) = Any $ f e
mapAggrFun f (Count e) = Count $ f e
mapAggrFun _ CountStar = CountStar
nameChangeProj :: (Attr, Expr) -> Either (Attr, Attr) (Attr, Expr)
nameChangeProj (c, ColE c') | c /= c' = Left (c, c')
nameChangeProj (c, e) = Right (c, e)
-- | If grouping columns are renamed, move renaming to a
-- projection. This makes plans more readable by keeping base table
-- names and should lead to more compact SQL code (less column
-- renaming)
pullProjectAggr :: TARule ()
pullProjectAggr q =
$(dagPatMatch 'q "Aggr args (q1)"
[| do
let (as, gs) = $(v "args")
-- Check wether there are projections that only change an
-- attribute name
let (gnps, gps) = partitionEithers $ map nameChangeProj gs
predicate $ not $ null gnps
-- Check that the original name in a grouping projection
-- does not collide with one of the output names for
-- aggregates.
predicate $ null $ (map snd gnps) `intersect` (map snd as)
return $ do
logRewrite "Basic.PullProject.Aggr" q
let gs' = nub $ gps ++ map (\(_, c) -> (c, ColE c)) gnps
proj = map (\(_, c) -> (c, ColE c)) as
++ map (\(c, _) -> (c, ColE c)) gps
++ map (\(c, c') -> (c, ColE c')) gnps
aggrNode <- insert $ UnOp (Aggr (as, gs')) $(v "q1")
void $ replaceWithNew q $ UnOp (Project proj) aggrNode |])
pullProjectWinFun :: TARule ()
pullProjectWinFun q =
$(dagPatMatch 'q "WinFun args (Project proj (q1))"
[| do
-- Only consider window functions without partitioning for
-- now. Partitioning requires proper values and inlining
-- would be problematic.
((resCol, f), [], sortSpec, frameSpec) <- return $(v "args")
-- If the window function result overwrites one of the
-- projection columns, we can't pull.
predicate $ resCol `notElem` (map fst $(v "proj"))
return $ do
logRewrite "Basic.PullProject.WinFun" q
-- Merge the projection expressions into window function
-- arguments and ordering expressions.
let f' = mapWinFun (inlineExpr $(v "proj")) f
sortSpec' = map (\(e, d) -> (inlineExpr $(v "proj") e, d)) sortSpec
proj' = $(v "proj") ++ [(resCol, ColE resCol)]
winNode <- insert $ UnOp (WinFun ((resCol, f'), [], sortSpec', frameSpec)) $(v "q1")
void $ replaceWithNew q $ UnOp (Project proj') winNode |])
pullProjectSerialize :: TARule ()
pullProjectSerialize q =
$(dagPatMatch 'q "Serialize args (Project proj (q1))"
[| do
return $ do
logRewrite "Basic.PullProject.Serialize" q
let (rcs, kcs, ocs, pcs) = $(v "args")
let rcs' = [ RefCol c (inlineExpr $(v "proj") e) | RefCol c e <- rcs ]
kcs' = [ KeyCol c (inlineExpr $(v "proj") e) | KeyCol c e <- kcs ]
ocs' = [ OrdCol c (inlineExpr $(v "proj") e) | OrdCol c e <- ocs ]
pcs' = [ PayloadCol c (inlineExpr $(v "proj") e)
| PayloadCol c e <- pcs
]
void $ replaceWithNew q $ UnOp (Serialize (rcs', kcs', ocs', pcs')) $(v "q1") |])
pullProjectSelect :: TARule ()
pullProjectSelect q =
$(dagPatMatch 'q "Select p (Project proj (q1))"
[| do
return $ do
logRewrite "Basic.PullProject.Select" q
let p' = inlineExpr $(v "proj") $(v "p")
selectNode <- insert $ UnOp (Select p') $(v "q1")
void $ replaceWithNew q $ UnOp (Project $(v "proj")) selectNode |])
mergeProjIntoSortSpec :: (Attr, [SortSpec], [PartExpr])
-> [(Attr, Expr)]
-> (Attr, [SortSpec], [PartExpr])
mergeProjIntoSortSpec (attr, sortSpec, partSpec) proj = (attr, sortSpec', partSpec')
where
sortSpec' = map (\(e, dir) -> (inlineExpr proj e, dir)) sortSpec
partSpec' = map (inlineExpr proj) partSpec
pullProjectRownum :: TARule ()
pullProjectRownum q =
$(dagPatMatch 'q "RowNum s (Project p (q1))"
[| do
let (resCol, _, _) = $(v "s")
-- If the rownum overwrites one of the columns generated by
-- the projection, remove it from the projection.
let p' = [ (a, e) | (a, e) <- $(v "p"), a /= resCol ]
-- Make sure that the rownum result column does not appear
-- in one of the projection expressions to avoid shadowing.
predicate $ not $ S.member resCol (S.unions $ map (exprCols . snd) p')
return $ do
logRewrite "Basic.PullProject.Rownum" q
let p'' = p' ++ [(resCol, ColE resCol)]
s' = mergeProjIntoSortSpec $(v "s") $(v "p")
rownumNode <- insert $ UnOp (RowNum s') $(v "q1")
void $ replaceWithNew q $ UnOp (Project p'') rownumNode |])
inlineJoinPredRight :: [Proj] -> [(Expr, Expr, JoinRel)] -> [(Expr, Expr, JoinRel)]
inlineJoinPredRight proj p = map inlineConjunct p
where
inlineConjunct (le, re, rel) = (le, inlineExpr proj re, rel)
inlineJoinPredLeft :: [Proj] -> [(Expr, Expr, JoinRel)] -> [(Expr, Expr, JoinRel)]
inlineJoinPredLeft proj p = map inlineConjunct p
where
inlineConjunct (le, re, rel) = (inlineExpr proj le, re, rel)
pullProjectSemiJoinLeft :: TARule ()
pullProjectSemiJoinLeft q =
$(dagPatMatch 'q "(Project proj (q1)) [SemiJoin | AntiJoin]@joinOp p (q2)"
[| do
return $ do
logRewrite "Basic.PullProject.SemiJoin.Left" q
let p' = inlineJoinPredLeft $(v "proj") $(v "p")
joinNode <- insert $ BinOp ($(v "joinOp") p') $(v "q1") $(v "q2")
void $ replaceWithNew q $ UnOp (Project $(v "proj")) joinNode |])
pullProjectSemiJoinRight :: TARule ()
pullProjectSemiJoinRight q =
$(dagPatMatch 'q "(q1) [SemiJoin | AntiJoin]@jop p (Project proj (q2))"
[| do
return $ do
logRewrite "Basic.PullProject.SemiJoin.Right" q
let p' = inlineJoinPredRight $(v "proj") $(v "p")
void $ replaceWithNew q $ BinOp ($(v "jop") p') $(v "q1") $(v "q2") |])
pullProjectThetaJoinLeft :: TARule AllProps
pullProjectThetaJoinLeft q =
$(dagPatMatch 'q "(Project p (q1)) [ThetaJoin | LeftOuterJoin]@op jp (q2)"
[| do
colsLeft <- fmap fst <$> pCols <$> bu <$> properties $(v "q1")
colsRight <- fmap fst <$> pCols <$> bu <$> properties $(v "q2")
predicate $ S.null $ S.intersection colsLeft colsRight
return $ do
logRewrite "Basic.PullProject.Join.Left" q
let jp' = inlineJoinPredLeft $(v "p") $(v "jp")
p' = $(v "p")
++
S.toList (fmap (\c -> (c, ColE c)) colsRight)
joinNode <- insert $ BinOp ($(v "op") jp') $(v "q1") $(v "q2")
void $ replaceWithNew q $ UnOp (Project p') joinNode |])
pullProjectThetaJoinRight :: TARule AllProps
pullProjectThetaJoinRight q =
$(dagPatMatch 'q "(q1) [ThetaJoin | LeftOuterJoin]@op jp (Project p (q2))"
[| do
colsLeft <- fmap fst <$> pCols <$> bu <$> properties $(v "q1")
colsRight <- fmap fst <$> pCols <$> bu <$> properties $(v "q2")
predicate $ S.null $ S.intersection colsLeft colsRight
return $ do
logRewrite "Basic.PullProject.Join.Right" q
let jp' = inlineJoinPredRight $(v "p") $(v "jp")
p' = S.toList (fmap (\c -> (c, ColE c)) colsLeft)
++
$(v "p")
joinNode <- insert $ BinOp ($(v "op") jp') $(v "q1") $(v "q2")
void $ replaceWithNew q $ UnOp (Project p') joinNode |])
pullProjectCrossLeft :: TARule AllProps
pullProjectCrossLeft q =
$(dagPatMatch 'q "(Project p (q1)) Cross _ (q2)"
[| do
colsLeft <- fmap fst <$> pCols <$> bu <$> properties $(v "q1")
colsRight <- fmap fst <$> pCols <$> bu <$> properties $(v "q2")
predicate $ S.null $ S.intersection colsLeft colsRight
return $ do
logRewrite "Basic.PullProject.Cross.Left" q
let p' = $(v "p")
++
S.toList (fmap (\c -> (c, ColE c)) colsRight)
crossNode <- insert $ BinOp (Cross ()) $(v "q1") $(v "q2")
void $ replaceWithNew q $ UnOp (Project p') crossNode |])
pullProjectCrossRight :: TARule AllProps
pullProjectCrossRight q =
$(dagPatMatch 'q "(q1) Cross _ (Project p (q2))"
[| do
colsLeft <- fmap fst <$> pCols <$> bu <$> properties $(v "q1")
colsRight <- fmap fst <$> pCols <$> bu <$> properties $(v "q2")
predicate $ S.null $ S.intersection colsLeft colsRight
return $ do
logRewrite "Basic.PullProject.Cross.Right" q
let p' = S.toList (fmap (\c -> (c, ColE c)) colsLeft)
++
$(v "p")
crossNode <- insert $ BinOp (Cross ()) $(v "q1") $(v "q2")
void $ replaceWithNew q $ UnOp (Project p') crossNode |])
inlineProjectAggr :: TARule ()
inlineProjectAggr q =
$(dagPatMatch 'q "Aggr args (Project p (q1))"
[| do
let (as, gs) = $(v "args")
let inline = inlineExpr $(v "p")
let as' = map (\(a, c) -> (mapAggrFun inline a, c)) as
gs' = map (\(c, e) -> (c, inline e)) gs
return $ do
logRewrite "Basic.PullProject.Aggr" q
void $ replaceWithNew q $ UnOp (Aggr (as', gs')) $(v "q1") |])
--------------------------------------------------------------------------------
-- Rewrites based on functional dependencies