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 ------------------------------------------------------------------------
61 -- | Returns the Tree of Nodes in Database
62 -- (without shared folders)
63 -- keeping this for teaching purpose only
64 treeDB' :: HasTreeError err
67 -> Cmd err (Tree NodeTree)
69 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
70 -- Same as (but easier to read) :
71 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
73 treeDB :: HasTreeError err
76 -> Cmd err (Tree NodeTree)
77 treeDB r nodeTypes = do
78 mainRoot <- dbTree r nodeTypes
79 sharedRoots <- findShared r nodeTypes
80 toTree $ toTreeParent (mainRoot <> sharedRoots)
82 ------------------------------------------------------------------------
83 -- | Collaborative Nodes in the Tree
84 findShared :: RootId -> [NodeType] -> Cmd err [DbTreeNode]
86 folderSharedId <- maybe (panic "no folder found") identity
88 <$> findNodesId r [NodeFolderShared]
89 folders <- getNodeNode folderSharedId
90 nodesSharedId <- mapM (\child -> sharedTree folderSharedId child nt)
91 $ map _nn_node2_id folders
92 pure $ concat nodesSharedId
94 sharedTree :: ParentId -> NodeId -> [NodeType] -> Cmd err [DbTreeNode]
95 sharedTree p n nt = dbTree n nt
96 <&> map (\n' -> if _dt_nodeId n' == n
97 then set dt_parentId (Just p) n'
100 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
101 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
102 findNodesId r nt = tail
105 ------------------------------------------------------------------------
106 ------------------------------------------------------------------------
107 toTree :: ( MonadError e m
109 => Map (Maybe ParentId) [DbTreeNode]
112 case lookup Nothing m of
113 Just [n] -> pure $ toTree' m n
114 Nothing -> treeError NoRoot
115 Just [] -> treeError EmptyRoot
116 Just _ -> treeError TooManyRoots
119 toTree' :: Map (Maybe ParentId) [DbTreeNode]
123 TreeN (toNodeTree n) $
124 m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
126 toNodeTree :: DbTreeNode
128 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
130 nodeType = fromNodeTypeId tId
131 ------------------------------------------------------------------------
132 toTreeParent :: [DbTreeNode]
133 -> Map (Maybe ParentId) [DbTreeNode]
134 toTreeParent = fromListWith (<>) . map (\n -> (_dt_parentId n, [n]))
135 ------------------------------------------------------------------------
136 -- | Main DB Tree function
139 -> Cmd err [DbTreeNode]
140 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
141 <$> runPGSQuery [sql|
143 tree (id, typename, parent_id, name) AS
145 SELECT p.id, p.typename, p.parent_id, p.name
151 SELECT c.id, c.typename, c.parent_id, c.name
154 INNER JOIN tree AS s ON c.parent_id = s.id
155 WHERE c.typename IN ?
158 |] (rootId, In typename)
160 typename = map nodeTypeId ns
161 ns = case nodeTypes of
165 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
166 isDescendantOf childId rootId = (== [Only True])
167 <$> runPGSQuery [sql|
169 SET TRANSACTION READ ONLY;
173 tree (id, parent_id) AS
175 SELECT c.id, c.parent_id
181 SELECT p.id, p.parent_id
183 INNER JOIN tree AS t ON t.parent_id = p.id
186 SELECT COUNT(*) = 1 from tree AS t
190 -- TODO should we check the category?
191 isIn :: NodeId -> DocId -> Cmd err Bool
192 isIn cId docId = ( == [Only True])
193 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
195 WHERE nn.node1_id = ?
198 -----------------------------------------------------