algebra-sql-0.3.0.0: src/Database/Algebra/Table/Render/Dot.hs
module Database.Algebra.Table.Render.Dot(renderTADot) where
import Data.List
import qualified Text.PrettyPrint.ANSI.Leijen as P
import qualified Database.Algebra.Dag as Dag
import Database.Algebra.Dag.Common
import Database.Algebra.Table.Lang
pp :: P.Pretty a => a -> String
pp a = (P.displayS $ P.renderPretty 0.9 120 $ P.pretty a) ""
nodeToDoc :: AlgNode -> P.Doc
nodeToDoc n = (P.text "id:") P.<+> (P.int n)
labelToDoc :: AlgNode -> String -> P.Doc -> P.Doc
labelToDoc n s as = (nodeToDoc n) P.<+> P.text "\\n" P.<+> ((P.text s) P.<> (P.parens as))
commas :: (a -> P.Doc) -> [a] -> P.Doc
commas f = P.hsep . P.punctuate P.comma . map f
renderProj :: Proj -> P.Doc
renderProj (new, ColE c) | new == c = P.text new
renderProj (new, e) = P.text new P.<> P.colon P.<> P.pretty e
renderAggr :: (AggrType, ResAttr) -> P.Doc
renderAggr (aggr, res) = P.text res P.<> P.colon P.<> P.pretty aggr
renderSortInf :: SortSpec -> P.Doc
renderSortInf (ColE c, Desc) = P.text c P.<+> P.text "/desc"
renderSortInf (expr, Desc) = (P.parens $ P.pretty expr) P.<> P.text "/desc"
renderSortInf (ColE c, Asc) = P.text c
renderSortInf (expr, Asc) = P.parens $ P.pretty expr
renderJoinArgs :: (Expr, Expr, JoinRel) -> P.Doc
renderJoinArgs (left, right, joinR) =
parenthize left P.<+> P.pretty joinR P.<+> parenthize right
renderPartExprs :: [PartExpr] -> P.Doc
renderPartExprs [] = P.empty
renderPartExprs es@(_:_) = P.text "/" P.<+> commas P.pretty es
renderKey :: Key -> P.Doc
renderKey (Key k) = P.brackets $ commas P.text k
renderColumn :: (Attr, ATy) -> P.Doc
renderColumn (c, t) = P.text c P.<+> P.text "::" P.<+> (P.pretty t)
renderTuple :: Tuple -> P.Doc
renderTuple = P.hcat . P.punctuate P.comma . map P.pretty
renderData :: [Tuple] -> P.Doc
renderData [] = P.empty
renderData xs = P.sep $ P.punctuate P.semi $ map renderTuple xs
renderSchema :: [TypedAttr] -> P.Doc
renderSchema cols = commas renderColumn cols
renderTableInfo :: TableName -> [(Attr, ATy)] -> [Key] -> P.Doc
renderTableInfo tableName cols keys =
(P.text tableName)
P.<+> P.text "\\n"
P.<+> (P.brackets $ renderSchema cols)
P.<+> P.text "\\n"
P.<+> (P.brackets $ commas renderKey keys)
opDotLabel :: AlgNode -> TALabel -> P.Doc
-- | Nullary operations
opDotLabel i (LitTableL dat schema) = labelToDoc i
"LITTABLE" (renderSchema schema P.<+> renderData dat)
opDotLabel i (TableRefL (name, attrs, keys)) = labelToDoc i
"TABLE" (renderTableInfo name attrs keys)
-- | Binary operations
opDotLabel i (CrossL _) = labelToDoc i
"CROSS" P.empty
opDotLabel i (EqJoinL (left,right)) = labelToDoc i
"EQJOIN" (P.text $ left ++ "," ++ right)
opDotLabel i (DifferenceL _) = labelToDoc i
"DIFF" P.empty
opDotLabel i (DisjUnionL _) = labelToDoc i
"UNION" P.empty
opDotLabel i (ThetaJoinL info) = labelToDoc i
"THETAJOIN" (commas renderJoinArgs info)
opDotLabel i (LeftOuterJoinL info) = labelToDoc i
"LEFTOUTERJOIN" (commas renderJoinArgs info)
opDotLabel i (SemiJoinL info) = labelToDoc i
"SEMIJOIN" (commas renderJoinArgs info)
opDotLabel i (AntiJoinL info) = labelToDoc i
"ANTIJOIN" (commas renderJoinArgs info)
-- | Unary operations
opDotLabel i (RowNumL (res,sortI,attr)) = labelToDoc i
"ROWNUM" ((P.text $ res ++ ":<")
P.<+> (commas renderSortInf sortI)
P.<+> P.text ">"
P.<+> renderPartExprs attr)
opDotLabel i (RowRankL (res,sortInf)) = labelToDoc i
"ROWRANK" ((P.text $ res ++ ":<")
P.<+> (commas renderSortInf sortInf)
P.<+> P.text ">")
opDotLabel i (RankL (res,sortInf)) = labelToDoc i
"RANK" ((P.text $ res ++ ":<")
P.<+> commas renderSortInf sortInf
P.<+> P.text ">")
opDotLabel i (ProjectL info) = labelToDoc i
"PROJECT" (commas renderProj info)
opDotLabel i (SelL info) = labelToDoc i
"SELECT" (P.pretty info)
opDotLabel i (DistinctL _) = labelToDoc i
"DISTINCT" P.empty
opDotLabel i (AggrL (aggrList, attr)) = labelToDoc i
"AGGR" (commas renderAggr aggrList
P.<+>
(P.brackets $ commas renderProj attr))
opDotLabel i (SerializeL (ref, key, ord, item)) = labelToDoc i
"SERIALIZE" (serializeArg "ref" ref P.<+> P.text "\n"
P.<+> serializeArg "key" key P.<+> P.text "\n"
P.<+> serializeArg "ord" ord P.<+> P.text "\n"
P.<+> serializeArg "items" item P.<+> P.text "\n")
opDotLabel i (WinFunL (winFuns, partSpec, sortSpec, mFrameBounds)) = labelToDoc i
"WIN" (P.hcat $ intersperse (P.text "\\n") [ renderWinFuns winFuns
, renderPartSpec partSpec
, renderSortSpec sortSpec
, maybe P.empty
renderFrameBounds
mFrameBounds
])
serializeArg :: P.Pretty a => String -> [a] -> P.Doc
serializeArg desc cols = P.text desc P.<+> P.equals
P.<+> P.brackets (commas P.pretty cols)
renderWinFun :: WinFun -> P.Doc
renderWinFun (WinMax e) = P.text "MAX" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinMin e) = P.text "MIN" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinSum e) = P.text "SUM" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinAvg e) = P.text "AVG" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinAll e) = P.text "ALL" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinAny e) = P.text "ANY" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinFirstValue e) = P.text "first_value" P.<+> (P.parens $ P.pretty e)
renderWinFun (WinLastValue e) = P.text "last_value" P.<+> (P.parens $ P.pretty e)
renderWinFun WinCount = P.text "COUNT()"
renderWinFuns :: (ResAttr, WinFun) -> P.Doc
renderWinFuns (c, f) = renderWinFun f P.<+> P.text "AS" P.<+> P.text c
renderPartSpec :: [PartExpr] -> P.Doc
renderPartSpec [] = P.empty
renderPartSpec as@(_:_) = P.text "PARTITION BY" P.<+> commas P.pretty as
renderSortSpec :: [SortSpec] -> P.Doc
renderSortSpec [] = P.empty
renderSortSpec ss@(_:_) = P.text "ORDER BY" P.<+> commas renderSortInf ss
renderFrameBounds :: FrameBounds -> P.Doc
renderFrameBounds (HalfOpenFrame fs) = renderFrameStart fs
renderFrameBounds (ClosedFrame fs fe) = renderFrameStart fs
P.<+> P.text "AND"
P.<+> renderFrameEnd fe
renderFrameStart :: FrameStart -> P.Doc
renderFrameStart FSUnboundPrec = P.text "UNBOUNDED PRECEDING"
renderFrameStart (FSValPrec i) = P.int i P.<+> P.text "PRECEDING"
renderFrameStart FSCurrRow = P.text "CURRENT ROW"
renderFrameEnd :: FrameEnd -> P.Doc
renderFrameEnd FEUnboundFol = P.text "UNBOUNDED FOLLOWING"
renderFrameEnd (FEValFol i) = P.int i P.<+> P.text "FOLLOWING"
renderFrameEnd FECurrRow = P.text "CURRENT ROW"
constructDotNode :: (AlgNode, TALabel) -> DotNode
constructDotNode (n, op) =
DotNode n l c Nothing
where l = pp $ opDotLabel n op
c = opDotColor op
-- | Create an abstract Dot edge
constructDotEdge :: (AlgNode, AlgNode) -> DotEdge
constructDotEdge = uncurry DotEdge
renderDotEdge :: DotEdge -> P.Doc
renderDotEdge (DotEdge u v) = P.int u P.<+> P.text "->" P.<+> P.int v P.<+> P.semi
renderColor :: DotColor -> P.Doc
renderColor DCTomato = P.text "tomato"
renderColor DCRed = P.text "red"
renderColor DCOrangeDCRed = P.text "orangered"
renderColor DCSalmon = P.text "salmon"
renderColor DCGray = P.text "gray"
renderColor DCDimDCGray = P.text "dimgray"
renderColor DCGold = P.text "gold"
renderColor DCTan = P.text "tan"
renderColor DCCrimson = P.text "crimson"
renderColor DCGreen = P.text "green"
renderColor DCSienna = P.text "sienna"
renderColor DCBeige = P.text "beige"
renderColor DCDodgerBlue = P.text "dodgerblue"
renderColor DCLightSkyBlue = P.text "lightskyblue"
renderColor DCDeepSkyBlue = P.text "deepskyblue"
renderColor DCGray52 = P.text "gray52"
renderColor DCGray91 = P.text "gray91"
renderColor DCDarkDCOrange = P.text "darkorange"
renderColor DCOrange = P.text "orange"
renderColor DCWhite = P.text "white"
renderColor DCCyan = P.text "cyan"
renderColor DCCyan4 = P.text "cyan4"
renderColor DCHotPink = P.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 (LeftOuterJoinL _) = DCDeepSkyBlue
opDotColor (SemiJoinL _) = DCLightSkyBlue
opDotColor (AntiJoinL _) = DCLightSkyBlue
renderDotNode :: DotNode -> P.Doc
renderDotNode (DotNode n l c s) =
P.int n
P.<+> (P.brackets $ (((P.text "label=") P.<+> (P.dquotes $ P.text l))
P.<+> P.comma
P.<+> (P.text "color=") P.<+> (renderColor c)
P.<+> styleDoc))
P.<+> P.semi
where styleDoc =
case s of
Just Solid -> P.comma P.<+> P.text "solid"
Nothing -> P.empty
preamble :: P.Doc
preamble = graphAttributes P.</> nodeAttributes
where
nodeAttributes = P.text "node [style=filled, shape=box];"
graphAttributes = P.text "ordering=out;"
-- | Dot colors
data DotColor = DCTomato
| DCSalmon
| DCGray
| DCDimDCGray
| DCGold
| DCTan
| DCRed
| DCOrangeDCRed
| DCCrimson
| DCGreen
| DCSienna
| DCBeige
| DCDodgerBlue
| DCLightSkyBlue
| DCDeepSkyBlue
| 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] -> P.Doc
renderDot ns es = P.text "digraph" P.<+> (P.braces $ preamble P.</> nodeSection P.</> edgeSection)
where nodeSection = P.vcat $ map renderDotNode ns
edgeSection = P.vcat $ map renderDotEdge es
-- | Labels (to collect all operations (nullary, unary,binary))
data TALabel = LitTableL [Tuple] [TypedAttr]
| 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)]
| LeftOuterJoinL [(Expr, Expr, JoinRel)]
| SerializeL ([RefCol], [KeyCol], [OrdCol], [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
labelOfBinOp (LeftOuterJoin info) = LeftOuterJoinL 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 :: [AlgNode] -> NodeMap TableAlgebra -> String
renderTADot roots m = pp $ renderDot dotNodes dotEdges
where (opLabels, edges) = extractGraphStructure labelOfOp d
d = Dag.mkDag m roots
dotNodes = map constructDotNode opLabels
dotEdges = map constructDotEdge edges