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
37 import Control.Lens (view, toListOf, at, each, _Just, to, set, makeLenses)
38 import Control.Monad.Except (MonadError())
39 import Data.List (tail, concat, nub)
40 import Data.Map (Map, fromListWith, lookup)
41 import qualified Data.Set as Set
42 import Data.Text (Text)
43 import Database.PostgreSQL.Simple
44 import Database.PostgreSQL.Simple.SqlQQ
46 import Gargantext.Prelude
48 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
49 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId, fromNodeTypeId)
50 import Gargantext.Database.Admin.Types.Node
51 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
52 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
53 import Gargantext.Database.Query.Tree.Error
54 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
56 ------------------------------------------------------------------------
57 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
59 , _dt_parentId :: Maybe NodeId
63 makeLenses ''DbTreeNode
65 instance Eq DbTreeNode where
66 (==) d1 d2 = (==) (_dt_nodeId d1) (_dt_nodeId d2)
68 ------------------------------------------------------------------------
70 data TreeMode = TreeBasic | TreeAdvanced | TreeFirstLevel
72 -- | Returns the Tree of Nodes in Database
73 tree :: HasTreeError err
77 -> Cmd err (Tree NodeTree)
78 tree TreeBasic = tree_basic
79 tree TreeAdvanced = tree_advanced
80 tree TreeFirstLevel = tree_first_level
82 -- | Tree basic returns the Tree of Nodes in Database
83 -- (without shared folders)
84 -- keeping this for teaching purpose only
85 tree_basic :: HasTreeError err
88 -> Cmd err (Tree NodeTree)
89 tree_basic r nodeTypes =
90 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
91 -- Same as (but easier to read) :
92 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
94 -- | Advanced mode of the Tree enables shared nodes
95 tree_advanced :: HasTreeError err
98 -> Cmd err (Tree NodeTree)
99 tree_advanced r nodeTypes = do
100 mainRoot <- findNodes r Private nodeTypes
101 sharedRoots <- findNodes r Shared nodeTypes
102 publicRoots <- findNodes r Public nodeTypes
103 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
105 -- | Fetch only first level of tree
106 tree_first_level :: HasTreeError err
109 -> Cmd err (Tree NodeTree)
110 tree_first_level r nodeTypes = do
111 mainRoot <- findNodes r Private nodeTypes
112 sharedRoots <- findNodes r Shared nodeTypes
113 publicRoots <- findNodes r Public nodeTypes
114 toTree $ toSubtreeParent (mainRoot <> sharedRoots <> publicRoots)
116 ------------------------------------------------------------------------
117 data NodeMode = Private | Shared | Public
119 findNodes :: HasTreeError err
123 -> Cmd err [DbTreeNode]
124 findNodes r Private nt = dbTree r nt
125 findNodes r Shared nt = findShared r NodeFolderShared nt sharedTreeUpdate
126 findNodes r Public nt = findShared r NodeFolderPublic nt publicTreeUpdate
128 ------------------------------------------------------------------------
129 -- | Collaborative Nodes in the Tree
130 findShared :: HasTreeError err
131 => RootId -> NodeType -> [NodeType] -> UpdateTree err
132 -> Cmd err [DbTreeNode]
133 findShared r nt nts fun = do
134 foldersSharedId <- findNodesId r [nt]
135 trees <- mapM (updateTree nts fun) foldersSharedId
139 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
141 updateTree :: HasTreeError err
142 => [NodeType] -> UpdateTree err -> RootId
143 -> Cmd err [DbTreeNode]
144 updateTree nts fun r = do
145 folders <- getNodeNode r
146 nodesSharedId <- mapM (fun r nts)
147 $ map _nn_node2_id folders
148 pure $ concat nodesSharedId
151 sharedTreeUpdate :: HasTreeError err => UpdateTree err
152 sharedTreeUpdate p nt n = dbTree n nt
153 <&> map (\n' -> if (view dt_nodeId n') == n
154 -- && elem (fromNodeTypeId $ _dt_typeId n') [NodeGraph]
155 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
156 then set dt_parentId (Just p) n'
159 publicTreeUpdate :: HasTreeError err => UpdateTree err
160 publicTreeUpdate p nt n = dbTree n nt
161 <&> map (\n' -> if _dt_nodeId n' == n
162 -- && (fromNodeTypeId $ _dt_typeId n') /= NodeGraph
163 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
164 then set dt_parentId (Just p) n'
169 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
170 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
171 findNodesId r nt = tail
174 ------------------------------------------------------------------------
175 ------------------------------------------------------------------------
176 toTree :: ( MonadError e m
179 => Map (Maybe ParentId) [DbTreeNode]
182 case lookup Nothing m of
183 Just [n] -> pure $ toTree' m n
184 Nothing -> treeError NoRoot
185 Just [] -> treeError EmptyRoot
186 Just _r -> treeError TooManyRoots
189 toTree' :: Map (Maybe ParentId) [DbTreeNode]
193 TreeN (toNodeTree n) $
194 -- | Lines below are equivalent computationally but not semantically
195 -- m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
196 toListOf (at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')) m'
198 toNodeTree :: DbTreeNode
200 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
203 ------------------------------------------------------------------------
204 toTreeParent :: [DbTreeNode]
205 -> Map (Maybe ParentId) [DbTreeNode]
206 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
207 ------------------------------------------------------------------------
208 toSubtreeParent :: [DbTreeNode]
209 -> Map (Maybe ParentId) [DbTreeNode]
210 toSubtreeParent ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
212 nodeIds = Set.fromList $ map (\n -> unNodeId $ _dt_nodeId n) ns
213 nullifiedParents = map nullifyParent ns
214 nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
215 nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
216 , _dt_parentId = Just pId
218 , _dt_name = name }) =
219 if Set.member (unNodeId pId) nodeIds then
222 DbTreeNode { _dt_nodeId = nId
224 , _dt_parentId = Nothing
226 ------------------------------------------------------------------------
227 -- | Main DB Tree function
230 -> Cmd err [DbTreeNode]
231 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
232 <$> runPGSQuery [sql|
234 tree (id, typename, parent_id, name) AS
236 SELECT p.id, p.typename, p.parent_id, p.name
242 SELECT c.id, c.typename, c.parent_id, c.name
245 INNER JOIN tree AS s ON c.parent_id = s.id
246 WHERE c.typename IN ?
249 |] (rootId, In typename)
251 typename = map nodeTypeId ns
252 ns = case nodeTypes of
256 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
257 isDescendantOf childId rootId = (== [Only True])
258 <$> runPGSQuery [sql|
260 SET TRANSACTION READ ONLY;
264 tree (id, parent_id) AS
266 SELECT c.id, c.parent_id
272 SELECT p.id, p.parent_id
274 INNER JOIN tree AS t ON t.parent_id = p.id
277 SELECT COUNT(*) = 1 from tree AS t
281 -- TODO should we check the category?
282 isIn :: NodeId -> DocId -> Cmd err Bool
283 isIn cId docId = ( == [Only True])
284 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
286 WHERE nn.node1_id = ?
289 -----------------------------------------------------