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 NodeFolderShared nodeTypes
93 publicRoots <- findShared r NodeFolderPublic nodeTypes
94 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
96 ------------------------------------------------------------------------
97 -- | Collaborative Nodes in the Tree
98 findShared :: RootId -> NodeType -> [NodeType] -> Cmd err [DbTreeNode]
99 findShared r nt nts = do
100 folderSharedId <- maybe (panic "no folder found") identity
102 <$> findNodesId r [nt]
103 folders <- getNodeNode folderSharedId
104 nodesSharedId <- mapM (\child -> sharedTree folderSharedId child nts)
105 $ map _nn_node2_id folders
106 pure $ concat nodesSharedId
108 sharedTree :: ParentId -> NodeId -> [NodeType] -> Cmd err [DbTreeNode]
109 sharedTree p n nt = dbTree n nt
110 <&> map (\n' -> if _dt_nodeId n' == n
111 then set dt_parentId (Just p) n'
114 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
115 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
116 findNodesId r nt = tail
119 ------------------------------------------------------------------------
120 ------------------------------------------------------------------------
121 toTree :: ( MonadError e m
123 => Map (Maybe ParentId) [DbTreeNode]
126 case lookup Nothing m of
127 Just [n] -> pure $ toTree' m n
128 Nothing -> treeError NoRoot
129 Just [] -> treeError EmptyRoot
130 Just _ -> treeError TooManyRoots
133 toTree' :: Map (Maybe ParentId) [DbTreeNode]
137 TreeN (toNodeTree n) $
138 m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
140 toNodeTree :: DbTreeNode
142 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
144 nodeType = fromNodeTypeId tId
145 ------------------------------------------------------------------------
146 toTreeParent :: [DbTreeNode]
147 -> Map (Maybe ParentId) [DbTreeNode]
148 toTreeParent = fromListWith (<>) . map (\n -> (_dt_parentId n, [n]))
149 ------------------------------------------------------------------------
150 -- | Main DB Tree function
153 -> Cmd err [DbTreeNode]
154 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
155 <$> runPGSQuery [sql|
157 tree (id, typename, parent_id, name) AS
159 SELECT p.id, p.typename, p.parent_id, p.name
165 SELECT c.id, c.typename, c.parent_id, c.name
168 INNER JOIN tree AS s ON c.parent_id = s.id
169 WHERE c.typename IN ?
172 |] (rootId, In typename)
174 typename = map nodeTypeId ns
175 ns = case nodeTypes of
179 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
180 isDescendantOf childId rootId = (== [Only True])
181 <$> runPGSQuery [sql|
183 SET TRANSACTION READ ONLY;
187 tree (id, parent_id) AS
189 SELECT c.id, c.parent_id
195 SELECT p.id, p.parent_id
197 INNER JOIN tree AS t ON t.parent_id = p.id
200 SELECT COUNT(*) = 1 from tree AS t
204 -- TODO should we check the category?
205 isIn :: NodeId -> DocId -> Cmd err Bool
206 isIn cId docId = ( == [Only True])
207 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
209 WHERE nn.node1_id = ?
212 -----------------------------------------------------