2 Module : Gargantext.Database.Tree
3 Description : Tree of Resource Nodes built from Database
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 Let a Root Node, return the Tree of the Node as a directed acyclic graph
13 -- TODO delete node, if not owned, then suppress the link only
14 -- see Action/Delete.hs
17 {-# LANGUAGE QuasiQuotes #-}
18 {-# LANGUAGE TemplateHaskell #-}
20 module Gargantext.Database.Query.Tree
21 ( module Gargantext.Database.Query.Tree.Error
35 import Control.Lens ((^..), at, each, _Just, to, set, makeLenses)
36 import Control.Monad.Error.Class (MonadError())
37 import Data.List (tail, concat)
38 import Data.Map (Map, fromListWith, lookup)
39 import Data.Text (Text)
40 import Database.PostgreSQL.Simple
41 import Database.PostgreSQL.Simple.SqlQQ
42 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
43 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId)
44 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType, DocId, allNodeTypes)
45 import Gargantext.Database.Admin.Types.Node -- (pgNodeId, NodeType(..))
46 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
47 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
48 import Gargantext.Database.Query.Tree.Error
49 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
50 import Gargantext.Prelude
52 ------------------------------------------------------------------------
53 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
55 , _dt_parentId :: Maybe NodeId
59 makeLenses ''DbTreeNode
60 ------------------------------------------------------------------------
62 data TreeMode = Basic | Advanced
64 -- | Returns the Tree of Nodes in Database
65 tree :: HasTreeError err
69 -> Cmd err (Tree NodeTree)
70 tree Basic = tree_basic
71 tree Advanced = tree_advanced
73 -- | Tree basic returns the Tree of Nodes in Database
74 -- (without shared folders)
75 -- keeping this for teaching purpose only
76 tree_basic :: HasTreeError err
79 -> Cmd err (Tree NodeTree)
80 tree_basic r nodeTypes =
81 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
82 -- Same as (but easier to read) :
83 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
85 -- | Advanced mode of the Tree enables shared nodes
86 tree_advanced :: HasTreeError err
89 -> Cmd err (Tree NodeTree)
90 tree_advanced r nodeTypes = do
91 mainRoot <- dbTree r nodeTypes
92 sharedRoots <- findShared r nodeTypes
93 toTree $ toTreeParent (mainRoot <> sharedRoots)
95 ------------------------------------------------------------------------
96 -- | Collaborative Nodes in the Tree
97 findShared :: RootId -> [NodeType] -> Cmd err [DbTreeNode]
99 folderSharedId <- maybe (panic "no folder found") identity
101 <$> findNodesId r [NodeFolderShared]
102 folders <- getNodeNode folderSharedId
103 nodesSharedId <- mapM (\child -> sharedTree folderSharedId child nt)
104 $ map _nn_node2_id folders
105 pure $ concat nodesSharedId
107 sharedTree :: ParentId -> NodeId -> [NodeType] -> Cmd err [DbTreeNode]
108 sharedTree p n nt = dbTree n nt
109 <&> map (\n' -> if _dt_nodeId n' == n
110 then set dt_parentId (Just p) n'
113 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
114 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
115 findNodesId r nt = tail
118 ------------------------------------------------------------------------
119 ------------------------------------------------------------------------
120 toTree :: ( MonadError e m
122 => Map (Maybe ParentId) [DbTreeNode]
125 case lookup Nothing m of
126 Just [n] -> pure $ toTree' m n
127 Nothing -> treeError NoRoot
128 Just [] -> treeError EmptyRoot
129 Just _ -> treeError TooManyRoots
132 toTree' :: Map (Maybe ParentId) [DbTreeNode]
136 TreeN (toNodeTree n) $
137 m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
139 toNodeTree :: DbTreeNode
141 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
143 nodeType = fromNodeTypeId tId
144 ------------------------------------------------------------------------
145 toTreeParent :: [DbTreeNode]
146 -> Map (Maybe ParentId) [DbTreeNode]
147 toTreeParent = fromListWith (<>) . map (\n -> (_dt_parentId n, [n]))
148 ------------------------------------------------------------------------
149 -- | Main DB Tree function
152 -> Cmd err [DbTreeNode]
153 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
154 <$> runPGSQuery [sql|
156 tree (id, typename, parent_id, name) AS
158 SELECT p.id, p.typename, p.parent_id, p.name
164 SELECT c.id, c.typename, c.parent_id, c.name
167 INNER JOIN tree AS s ON c.parent_id = s.id
168 WHERE c.typename IN ?
171 |] (rootId, In typename)
173 typename = map nodeTypeId ns
174 ns = case nodeTypes of
178 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
179 isDescendantOf childId rootId = (== [Only True])
180 <$> runPGSQuery [sql|
182 SET TRANSACTION READ ONLY;
186 tree (id, parent_id) AS
188 SELECT c.id, c.parent_id
194 SELECT p.id, p.parent_id
196 INNER JOIN tree AS t ON t.parent_id = p.id
199 SELECT COUNT(*) = 1 from tree AS t
203 -- TODO should we check the category?
204 isIn :: NodeId -> DocId -> Cmd err Bool
205 isIn cId docId = ( == [Only True])
206 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
208 WHERE nn.node1_id = ?
211 -----------------------------------------------------