algebra-sql-0.1.0.0: src/Database/Algebra/Table/Render/Dot.hs
module Database.Algebra.Table.Render.Dot(renderTADot) where
import qualified Data.IntMap as Map
import Data.List
import Text.PrettyPrint
import qualified Database.Algebra.Dag as Dag
import Database.Algebra.Dag.Common
import Database.Algebra.Table.Lang
nodeToDoc :: AlgNode -> Doc
nodeToDoc n = (text "id:") <+> (int n)
tagsToDoc :: [Tag] -> Doc
tagsToDoc ts = vcat $ map text ts
labelToDoc :: AlgNode -> String -> Doc -> [Tag] -> Doc
labelToDoc n s as ts = (nodeToDoc n) <> text "\\n" <> ((text s) <> (parens as)) <> text "\\n" <> (tagsToDoc $ nub ts)
lookupTags :: AlgNode -> NodeMap [Tag] -> [Tag]
lookupTags n m = Map.findWithDefault [] n m
commas :: (a -> Doc) -> [a] -> Doc
commas f = hsep . punctuate comma . map f
renderProj :: Proj -> Doc
renderProj (new, ColE c) | new == c = text new
renderProj (new, e) = text $ concat [new, ":", show e]
renderAggr :: (AggrType, ResAttr) -> Doc
renderAggr (aggr, res) = text $ res ++ ":" ++ show aggr
renderSortInf :: SortSpec -> Doc
renderSortInf (ColE c, Desc) = text c <> text "/desc"
renderSortInf (expr, Desc) = (parens $ text (show expr)) <> text "/desc"
renderSortInf (ColE c, Asc) = text c
renderSortInf (expr, Asc) = parens $ text (show expr)
renderJoinArgs :: (Expr, Expr, JoinRel) -> Doc
renderJoinArgs (left, right, joinR) =
(text $ show left) <+> (text $ show joinR) <+> (text $ show right)
renderPartExprs :: [PartExpr] -> Doc
renderPartExprs [] = empty
renderPartExprs es@(_:_) = text "/" <> commas (text . show) es
renderKey :: Key -> Doc
renderKey (Key k) = brackets $ commas text k
renderColumn :: (Attr, ATy) -> Doc
renderColumn (c, t) = text c <> text "::" <> (text $ show t)
renderTuple :: Tuple -> Doc
renderTuple = hcat . punctuate comma . map (text . show)
renderData :: [Tuple] -> Doc
renderData [] = empty
renderData xs = sep $ punctuate semi $ map renderTuple xs
renderTableInfo :: TableName -> [(Attr, ATy)] -> [Key] -> Doc
renderTableInfo tableName cols keys =
(text tableName)
<> text "\\n"
<> (brackets $ commas renderColumn cols)
<> text "\\n"
<> (brackets $ commas renderKey keys)
opDotLabel :: NodeMap [Tag] -> AlgNode -> TALabel -> Doc
-- | Nullary operations
opDotLabel tags i (LitTableL dat _schema) = labelToDoc i
"LITTABLE" (renderData dat) (lookupTags i tags)
opDotLabel tags i (TableRefL (name, attrs, keys)) = labelToDoc i
"TABLE" (renderTableInfo name attrs keys) (lookupTags i tags)
-- | Binary operations
opDotLabel tags i (CrossL _) = labelToDoc i
"CROSS" empty (lookupTags i tags)
opDotLabel tags i (EqJoinL (left,right)) = labelToDoc i
"EQJOIN" (text $ left ++ "," ++ right) (lookupTags i tags)
opDotLabel tags i (DifferenceL _) = labelToDoc i
"DIFF" empty (lookupTags i tags)
opDotLabel tags i (DisjUnionL _) = labelToDoc i
"UNION" empty (lookupTags i tags)
opDotLabel tags i (ThetaJoinL info) = labelToDoc i
"THETAJOIN" (commas renderJoinArgs info) (lookupTags i tags)
opDotLabel tags i (SemiJoinL info) = labelToDoc i
"SEMIJOIN" (commas renderJoinArgs info) (lookupTags i tags)
opDotLabel tags i (AntiJoinL info) = labelToDoc i
"ANTIJOIN" (commas renderJoinArgs info) (lookupTags i tags)
-- | Unary operations
opDotLabel tags i (RowNumL (res,sortI,attr)) = labelToDoc i
"ROWNUM" ((text $ res ++ ":<")
<> (commas renderSortInf sortI)
<> text ">"
<> renderPartExprs attr)
(lookupTags i tags)
opDotLabel tags i (RowRankL (res,sortInf)) = labelToDoc i
"ROWRANK" ((text $ res ++ ":<")
<> (commas renderSortInf sortInf)
<> text ">")
(lookupTags i tags)
opDotLabel tags i (RankL (res,sortInf)) = labelToDoc i
"RANK" ((text $ res ++ ":<")
<> commas renderSortInf sortInf
<> text ">")
(lookupTags i tags)
opDotLabel tags i (ProjectL info) = labelToDoc i
"PROJECT" (commas renderProj info) (lookupTags i tags)
opDotLabel tags i (SelL info) = labelToDoc i
"SELECT" (text $ show info) (lookupTags i tags)
opDotLabel tags i (DistinctL _) = labelToDoc i
"DISTINCT" empty (lookupTags i tags)
opDotLabel tags i (AggrL (aggrList, attr)) = labelToDoc i
"AGGR" ((commas renderAggr aggrList) <+> (brackets $ commas renderProj attr))
(lookupTags i tags)
opDotLabel tags i (SerializeL (mDescr, mPos, cols)) = labelToDoc i
"SERIALIZE" (renderSerCol mDescr
<+> (text $ show mPos)
<+> (brackets $ commas (text . show) cols))
(lookupTags i tags)
opDotLabel tags i (WinFunL (winFuns, partSpec, sortSpec, mFrameBounds)) = labelToDoc i
"WIN" (hcat $ intersperse (text "\\n") [ renderWinFuns winFuns
, renderPartSpec partSpec
, renderSortSpec sortSpec
, maybe empty renderFrameBounds mFrameBounds
])
(lookupTags i tags)
renderWinFun :: WinFun -> Doc
renderWinFun (WinMax e) = text "MAX" <> (parens $ text $ show e)
renderWinFun (WinMin e) = text "MIN" <> (parens $ text $ show e)
renderWinFun (WinSum e) = text "SUM" <> (parens $ text $ show e)
renderWinFun (WinAvg e) = text "AVG" <> (parens $ text $ show e)
renderWinFun (WinAll e) = text "ALL" <> (parens $ text $ show e)
renderWinFun (WinAny e) = text "ANY" <> (parens $ text $ show e)
renderWinFun (WinFirstValue e) = text "first_value" <> (parens $ text $ show e)
renderWinFun (WinLastValue e) = text "last_value" <> (parens $ text $ show e)
renderWinFun WinCount = text "COUNT()"
renderWinFuns :: (ResAttr, WinFun) -> Doc
renderWinFuns (c, f) = renderWinFun f <+> text "AS" <+> text c
renderPartSpec :: [PartExpr] -> Doc
renderPartSpec [] = empty
renderPartSpec as@(_:_) = text "PARTITION BY" <+> commas (text . show) as
renderSortSpec :: [SortSpec] -> Doc
renderSortSpec [] = empty
renderSortSpec ss@(_:_) = text "ORDER BY" <+> commas renderSortInf ss
renderFrameBounds :: FrameBounds -> Doc
renderFrameBounds (HalfOpenFrame fs) = renderFrameStart fs
renderFrameBounds (ClosedFrame fs fe) = renderFrameStart fs
<+> text "AND"
<+> renderFrameEnd fe
renderFrameStart :: FrameStart -> Doc
renderFrameStart FSUnboundPrec = text "UNBOUNDED PRECEDING"
renderFrameStart (FSValPrec i) = int i <+> text "PRECEDING"
renderFrameStart FSCurrRow = text "CURRENT ROW"
renderFrameEnd :: FrameEnd -> Doc
renderFrameEnd FEUnboundFol = text "UNBOUNDED FOLLOWING"
renderFrameEnd (FEValFol i) = int i <+> text "FOLLOWING"
renderFrameEnd FECurrRow = text "CURRENT ROW"
renderSerCol :: Show c => Maybe c -> Doc
renderSerCol Nothing = empty
renderSerCol (Just c) = (text $ show c) <> comma
constructDotNode :: NodeMap [Tag] -> (AlgNode, TALabel) -> DotNode
constructDotNode tags (n, op) =
DotNode n l c Nothing
where l = render $ opDotLabel tags n op
c = opDotColor op
-- | Create an abstract Dot edge
constructDotEdge :: (AlgNode, AlgNode) -> DotEdge
constructDotEdge = uncurry DotEdge
renderDotEdge :: DotEdge -> Doc
renderDotEdge (DotEdge u v) = int u <+> text "->" <+> int v <> semi
renderColor :: DotColor -> Doc
renderColor DCTomato = text "tomato"
renderColor DCRed = text "red"
renderColor DCOrangeDCRed = text "orangered"
renderColor DCSalmon = text "salmon"
renderColor DCGray = text "gray"
renderColor DCDimDCGray = text "dimgray"
renderColor DCGold = text "gold"
renderColor DCTan = text "tan"
renderColor DCCrimson = text "crimson"
renderColor DCGreen = text "green"
renderColor DCSienna = text "sienna"
renderColor DCBeige = text "beige"
renderColor DCDodgerBlue = text "dodgerblue"
renderColor DCLightSkyBlue = text "lightskyblue"
renderColor DCGray52 = text "gray52"
renderColor DCGray91 = text "gray91"
renderColor DCDarkDCOrange = text "darkorange"
renderColor DCOrange = text "orange"
renderColor DCWhite = text "white"
renderColor DCCyan = text "cyan"
renderColor DCCyan4 = text "cyan4"
renderColor DCHotPink = text "hotpink"
opDotColor :: TALabel -> DotColor
-- | Nullaryops
opDotColor (LitTableL _ _) = DCGray52
opDotColor (TableRefL _) = DCGray52
-- | Unops
opDotColor (ProjectL _) = DCGray91
opDotColor (SerializeL _) = DCHotPink
opDotColor (SelL _) = DCCyan
opDotColor (DistinctL _) = DCTan
opDotColor (AggrL _) = DCGold
opDotColor (RankL _) = DCTomato
opDotColor (RowNumL _) = DCRed
opDotColor (RowRankL _) = DCRed
opDotColor (WinFunL _) = DCSalmon
-- | Binops
opDotColor (CrossL _) = DCOrangeDCRed
opDotColor (DifferenceL _) = DCDarkDCOrange
opDotColor (DisjUnionL _) = DCOrange
opDotColor (EqJoinL _) = DCGreen
opDotColor (ThetaJoinL _) = DCDodgerBlue
opDotColor (SemiJoinL _) = DCLightSkyBlue
opDotColor (AntiJoinL _) = DCLightSkyBlue
renderDotNode :: DotNode -> Doc
renderDotNode (DotNode n l c s) =
int n
<+> (brackets $ (((text "label=") <> (doubleQuotes $ text l))
<> comma
<+> (text "color=") <> (renderColor c)
<> styleDoc))
<> semi
where styleDoc =
case s of
Just Solid -> comma <+> text "solid"
Nothing -> empty
preamble :: Doc
preamble = graphAttributes $$ nodeAttributes
where nodeAttributes = text "node" <+> (brackets $ text "style=filled" <> comma <+> text "shape=box") <> semi
graphAttributes = text "ordering=out;"
-- | Dot colors
data DotColor = DCTomato
| DCSalmon
| DCGray
| DCDimDCGray
| DCGold
| DCTan
| DCRed
| DCOrangeDCRed
| DCCrimson
| DCGreen
| DCSienna
| DCBeige
| DCDodgerBlue
| DCLightSkyBlue
| DCGray91
| DCGray52
| DCDarkDCOrange
| DCOrange
| DCCyan
| DCCyan4
| DCWhite
| DCHotPink
-- Type of Dot style options
data DotStyle = Solid
-- label of Dot nodes
type DotLabel = String
-- id of Dot nodes
type DotNodeID = Int
-- Type of Dot nodes
data DotNode = DotNode DotNodeID DotLabel DotColor (Maybe DotStyle)
-- Type of Dot edges
data DotEdge = DotEdge DotNodeID DotNodeID
-- | Render a Dot document from the preamble, nodes and edges
renderDot :: [DotNode] -> [DotEdge] -> Doc
renderDot ns es = text "digraph" <> (braces $ preamble $$ nodeSection $$ edgeSection)
where nodeSection = vcat $ map renderDotNode ns
edgeSection = vcat $ map renderDotEdge es
-- | Labels (to collect all operations (nullary, unary,binary))
data TALabel = LitTableL [Tuple] SchemaInfos
| TableRefL (TableName, [TypedAttr], [Key])
| AggrL ([(AggrType, ResAttr)], [(PartAttr, Expr)])
| WinFunL ((ResAttr, WinFun), [PartExpr], [SortSpec], Maybe FrameBounds)
| DistinctL ()
| ProjectL [Proj]
| RankL (ResAttr, [SortSpec])
| RowNumL (Attr, [SortSpec], [PartExpr])
| RowRankL (ResAttr, [SortSpec])
| SelL Expr
| CrossL ()
| DifferenceL ()
| DisjUnionL ()
| EqJoinL (LeftAttr,RightAttr)
| ThetaJoinL [(Expr, Expr, JoinRel)]
| SemiJoinL [(Expr, Expr, JoinRel)]
| AntiJoinL [(Expr, Expr, JoinRel)]
| SerializeL (Maybe DescrCol, SerializeOrder, [PayloadCol])
labelOfOp :: TableAlgebra -> TALabel
labelOfOp (Database.Algebra.Dag.Common.BinOp op _ _) = labelOfBinOp op
labelOfOp (Database.Algebra.Dag.Common.UnOp op _) = labelOfUnOp op
labelOfOp (Database.Algebra.Dag.Common.NullaryOp op) = labelOfNullaryOp op
labelOfOp (TerOp _ _ _ _) = error "no tertiary operations"
labelOfBinOp :: BinOp -> TALabel
labelOfBinOp (Cross info) = CrossL info
labelOfBinOp (Difference info) = DifferenceL info
labelOfBinOp (DisjUnion info) = DisjUnionL info
labelOfBinOp (EqJoin info) = EqJoinL info
labelOfBinOp (ThetaJoin info) = ThetaJoinL info
labelOfBinOp (SemiJoin info) = SemiJoinL info
labelOfBinOp (AntiJoin info) = AntiJoinL info
labelOfUnOp :: UnOp -> TALabel
labelOfUnOp (WinFun info) = WinFunL info
labelOfUnOp (Aggr info) = AggrL info
labelOfUnOp (Distinct info) = DistinctL info
labelOfUnOp (Project info) = ProjectL info
labelOfUnOp (Rank info) = RankL info
labelOfUnOp (RowNum info) = RowNumL info
labelOfUnOp (RowRank info) = RowRankL info
labelOfUnOp (Select info) = SelL info
labelOfUnOp (Serialize info) = SerializeL info
labelOfNullaryOp :: NullOp -> TALabel
labelOfNullaryOp (LitTable (tups, schema)) = LitTableL tups schema
labelOfNullaryOp (TableRef info) = TableRefL info
-- | extract the operator descriptions and list of edges from a DAG
extractGraphStructure :: Dag.Operator a => (a -> TALabel)
-> Dag.AlgebraDag a
-> ([(AlgNode, TALabel)], [(AlgNode, AlgNode)])
extractGraphStructure toLabel d = (labels, childs)
where nodes = Dag.topsort d
operators = zip nodes $ map (flip Dag.operator d) nodes
labels = map (\(n, op) -> (n, toLabel op)) operators
childs = concat $ map (\(n, op) -> zip (repeat n) (Dag.opChildren op)) operators
-- | Render an TableAlgebra plan into a dot file (GraphViz).
renderTADot :: NodeMap [Tag] -> [AlgNode] -> NodeMap TableAlgebra -> String
renderTADot ts roots m = render $ renderDot dotNodes dotEdges
where (opLabels, edges) = extractGraphStructure labelOfOp d
d = Dag.mkDag m roots
dotNodes = map (constructDotNode ts) opLabels
dotEdges = map constructDotEdge edges