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
42 import Control.Lens (view, toListOf, at, each, _Just, to, set, makeLenses)
43 import Control.Monad.Error.Class (MonadError())
44 import Data.List (tail, concat, nub)
45 import qualified Data.List as List
46 import Data.Map (Map, fromListWith, lookup)
47 -- import Data.Monoid (mconcat)
49 -- import qualified Data.Set as Set
50 import Data.Text (Text)
51 import Database.PostgreSQL.Simple
52 import Database.PostgreSQL.Simple.SqlQQ
54 import Gargantext.Prelude
55 import Gargantext.Core
56 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
57 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId, fromNodeTypeId)
58 import Gargantext.Database.Admin.Types.Hyperdata.Any (HyperdataAny)
59 import Gargantext.Database.Admin.Types.Node
60 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
61 import Gargantext.Database.Query.Table.Node (getNodeWith)
62 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
63 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
64 import Gargantext.Database.Query.Tree.Error
65 import Gargantext.Database.Schema.Node (NodePoly(..))
66 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
68 ------------------------------------------------------------------------
69 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
71 , _dt_parentId :: Maybe NodeId
75 makeLenses ''DbTreeNode
77 instance Eq DbTreeNode where
78 (==) d1 d2 = (==) (_dt_nodeId d1) (_dt_nodeId d2)
80 ------------------------------------------------------------------------
82 data TreeMode = TreeBasic | TreeAdvanced | TreeFirstLevel
84 -- | Returns the Tree of Nodes in Database
85 tree :: (HasTreeError err, HasNodeError err)
89 -> Cmd err (Tree NodeTree)
90 tree TreeBasic = tree_basic
91 tree TreeAdvanced = tree_advanced
92 tree TreeFirstLevel = tree_first_level
94 -- | Tree basic returns the Tree of Nodes in Database
95 -- (without shared folders)
96 -- keeping this for teaching purpose only
97 tree_basic :: (HasTreeError err, HasNodeError err)
101 -> Cmd err (Tree NodeTree)
102 tree_basic r nodeTypes =
103 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
104 -- Same as (but easier to read) :
105 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
107 -- | Advanced mode of the Tree enables shared nodes
108 tree_advanced :: (HasTreeError err, HasNodeError err)
111 -> Cmd err (Tree NodeTree)
112 tree_advanced r nodeTypes = do
113 -- let rPrefix s = "[tree_advanced] root = " <> show r <> " " <> s
114 mainRoot <- findNodes r Private nodeTypes
115 -- printDebug (rPrefix "mainRoot") mainRoot
116 publicRoots <- findNodes r Public nodeTypes
117 -- printDebug (rPrefix "publicRoots") publicRoots
118 sharedRoots <- findNodes r Shared nodeTypes
119 -- printDebug (rPrefix "sharedRoots") sharedRoots
120 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
122 -- | Fetch only first level of tree
123 tree_first_level :: (HasTreeError err, HasNodeError err)
126 -> Cmd err (Tree NodeTree)
127 tree_first_level r nodeTypes = do
128 -- let rPrefix s = mconcat [ "[tree_first_level] root = "
130 -- , ", nodeTypes = "
134 mainRoot <- findNodes r Private nodeTypes
135 -- printDebug (rPrefix "mainRoot") mainRoot
136 publicRoots <- findNodes r Public nodeTypes
137 -- printDebug (rPrefix "publicRoots") publicRoots
138 sharedRoots <- findNodes r SharedDirect nodeTypes
139 -- printDebug (rPrefix "sharedRoots") sharedRoots
140 ret <- toTree $ toSubtreeParent r (mainRoot <> sharedRoots <> publicRoots)
141 -- printDebug (rPrefix "tree") ret
144 ------------------------------------------------------------------------
145 data NodeMode = Private | Shared | Public | SharedDirect
147 findNodes :: (HasTreeError err, HasNodeError err)
151 -> Cmd err [DbTreeNode]
152 findNodes r Private nt = dbTree r nt
153 findNodes r Shared nt = findShared r NodeFolderShared nt sharedTreeUpdate
154 findNodes r SharedDirect nt = findSharedDirect r NodeFolderShared nt sharedTreeUpdate
155 findNodes r Public nt = findShared r NodeFolderPublic nt publicTreeUpdate
157 ------------------------------------------------------------------------
158 -- | Collaborative Nodes in the Tree
159 -- Queries the `nodes_nodes` table.
160 findShared :: HasTreeError err
161 => RootId -> NodeType -> [NodeType] -> UpdateTree err
162 -> Cmd err [DbTreeNode]
163 findShared r nt nts fun = do
164 foldersSharedId <- findNodesId r [nt]
165 trees <- mapM (updateTree nts fun) foldersSharedId
168 -- | Find shared folders with "direct" access, i.e. when fetching only
169 -- first-level subcomponents. This works in a simplified manner: fetch the node
170 -- and get the tree for its parent.
171 findSharedDirect :: (HasTreeError err, HasNodeError err)
172 => RootId -> NodeType -> [NodeType] -> UpdateTree err
173 -> Cmd err [DbTreeNode]
174 findSharedDirect r nt nts fun = do
175 -- let rPrefix s = mconcat [ "[findSharedDirect] r = "
183 parent <- getNodeWith r (Proxy :: Proxy HyperdataAny)
184 let mParent = _node_parentId parent
188 foldersSharedId <- findNodesId parentId [nt]
189 -- printDebug (rPrefix "foldersSharedId") foldersSharedId
190 trees <- mapM (updateTree nts fun) foldersSharedId
191 -- printDebug (rPrefix "trees") trees
195 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
197 updateTree :: HasTreeError err
198 => [NodeType] -> UpdateTree err -> RootId
199 -> Cmd err [DbTreeNode]
200 updateTree nts fun r = do
201 folders <- getNodeNode r
202 nodesSharedId <- mapM (fun r nts)
203 $ map _nn_node2_id folders
204 pure $ concat nodesSharedId
207 sharedTreeUpdate :: HasTreeError err => UpdateTree err
208 sharedTreeUpdate p nt n = dbTree n nt
209 <&> map (\n' -> if (view dt_nodeId n') == n
210 -- && elem (fromDBid $ _dt_typeId n') [NodeGraph]
211 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
212 then set dt_parentId (Just p) n'
215 publicTreeUpdate :: HasTreeError err => UpdateTree err
216 publicTreeUpdate p nt n = dbTree n nt
217 <&> map (\n' -> if _dt_nodeId n' == n
218 -- && (fromDBid $ _dt_typeId n') /= NodeGraph
219 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
220 then set dt_parentId (Just p) n'
225 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
226 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
227 findNodesId r nt = tail
231 findNodesWithType :: RootId -> [NodeType] -> [NodeType] -> Cmd err [DbTreeNode]
232 findNodesWithType root target through =
233 filter isInTarget <$> dbTree root through
235 isInTarget n = List.elem (fromDBid $ view dt_typeId n)
236 $ List.nub $ target <> through
238 ------------------------------------------------------------------------
239 ------------------------------------------------------------------------
240 toTree :: ( MonadError e m
243 => Map (Maybe ParentId) [DbTreeNode]
246 case lookup Nothing m of
247 Just [root] -> pure $ toTree' m root
248 Nothing -> treeError NoRoot
249 Just [] -> treeError EmptyRoot
250 Just _r -> treeError TooManyRoots
253 toTree' :: Map (Maybe ParentId) [DbTreeNode]
257 TreeN (toNodeTree root) $
258 -- | Lines below are equivalent computationally but not semantically
259 -- m' ^.. at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')
260 toListOf (at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')) m'
262 toNodeTree :: DbTreeNode
264 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
266 ------------------------------------------------------------------------
267 toTreeParent :: [DbTreeNode]
268 -> Map (Maybe ParentId) [DbTreeNode]
269 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
270 ------------------------------------------------------------------------
271 -- toSubtreeParent' :: [DbTreeNode]
272 -- -> Map (Maybe ParentId) [DbTreeNode]
273 -- toSubtreeParent' ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
275 -- nodeIds = Set.fromList $ map (\n -> unNodeId $ _dt_nodeId n) ns
276 -- nullifiedParents = map nullifyParent ns
277 -- nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
278 -- nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
279 -- , _dt_parentId = Just pId
280 -- , _dt_typeId = tId
281 -- , _dt_name = name }) =
282 -- if Set.member (unNodeId pId) nodeIds then
285 -- DbTreeNode { _dt_nodeId = nId
286 -- , _dt_typeId = tId
287 -- , _dt_parentId = Nothing
288 -- , _dt_name = name }
289 ------------------------------------------------------------------------
290 toSubtreeParent :: RootId
292 -> Map (Maybe ParentId) [DbTreeNode]
293 toSubtreeParent r ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
295 nullifiedParents = map nullifyParent ns
296 nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
297 nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
298 , _dt_parentId = _pId
300 , _dt_name = name }) =
302 DbTreeNode { _dt_nodeId = nId
304 , _dt_parentId = Nothing
308 ------------------------------------------------------------------------
309 -- | Main DB Tree function
312 -> Cmd err [DbTreeNode]
313 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
314 <$> runPGSQuery [sql|
316 tree (id, typename, parent_id, name) AS
318 SELECT p.id, p.typename, p.parent_id, p.name
324 SELECT c.id, c.typename, c.parent_id, c.name
327 INNER JOIN tree AS s ON c.parent_id = s.id
328 WHERE c.typename IN ?
331 |] (rootId, In typename)
333 typename = map nodeTypeId ns
334 ns = case nodeTypes of
338 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
339 isDescendantOf childId rootId = (== [Only True])
340 <$> runPGSQuery [sql|
342 SET TRANSACTION READ ONLY;
346 tree (id, parent_id) AS
348 SELECT c.id, c.parent_id
354 SELECT p.id, p.parent_id
356 INNER JOIN tree AS t ON t.parent_id = p.id
359 SELECT COUNT(*) = 1 from tree AS t
363 -- TODO should we check the category?
364 isIn :: NodeId -> DocId -> Cmd err Bool
365 isIn cId docId = ( == [Only True])
366 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
368 WHERE nn.node1_id = ?
371 -----------------------------------------------------