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
43 import Control.Lens (view, toListOf, at, each, _Just, to, set, makeLenses)
44 import Control.Monad.Error.Class (MonadError())
45 import Data.List (tail, concat, nub)
46 import qualified Data.List as List
47 import qualified Data.Text as Text
48 import Data.Map.Strict (Map, fromListWith, lookup)
49 -- import Data.Monoid (mconcat)
51 -- import qualified Data.Set as Set
52 import Data.Text (Text)
53 import Database.PostgreSQL.Simple
54 import Database.PostgreSQL.Simple.SqlQQ
56 import Gargantext.Prelude
57 import Gargantext.Core
58 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
59 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId, fromNodeTypeId)
60 import Gargantext.Database.Admin.Types.Hyperdata.Any (HyperdataAny)
61 import Gargantext.Database.Admin.Types.Node
62 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
63 import Gargantext.Database.Query.Table.Node (getNodeWith)
64 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
65 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
66 import Gargantext.Database.Query.Tree.Error
67 import Gargantext.Database.Schema.Node (NodePoly(..))
68 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
70 ------------------------------------------------------------------------
71 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
73 , _dt_parentId :: Maybe NodeId
77 makeLenses ''DbTreeNode
79 instance Eq DbTreeNode where
80 (==) d1 d2 = (==) (_dt_nodeId d1) (_dt_nodeId d2)
82 ------------------------------------------------------------------------
84 data TreeMode = TreeBasic | TreeAdvanced | TreeFirstLevel
86 -- | Returns the Tree of Nodes in Database
87 tree :: (HasTreeError err, HasNodeError err)
91 -> Cmd err (Tree NodeTree)
92 tree TreeBasic = tree_basic
93 tree TreeAdvanced = tree_advanced
94 tree TreeFirstLevel = tree_first_level
96 -- | Tree basic returns the Tree of Nodes in Database
97 -- (without shared folders)
98 -- keeping this for teaching purpose only
99 tree_basic :: (HasTreeError err, HasNodeError err)
102 -> Cmd err (Tree NodeTree)
103 tree_basic r nodeTypes =
104 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
105 -- Same as (but easier to read) :
106 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
108 -- | Advanced mode of the Tree enables shared nodes
109 tree_advanced :: (HasTreeError err, HasNodeError err)
112 -> Cmd err (Tree NodeTree)
113 tree_advanced r nodeTypes = do
114 -- let rPrefix s = "[tree_advanced] root = " <> show r <> " " <> s
115 mainRoot <- findNodes r Private nodeTypes
116 -- printDebug (rPrefix "mainRoot") mainRoot
117 publicRoots <- findNodes r Public nodeTypes
118 -- printDebug (rPrefix "publicRoots") publicRoots
119 sharedRoots <- findNodes r Shared nodeTypes
120 -- printDebug (rPrefix "sharedRoots") sharedRoots
121 -- let ret = toSubtreeParent r (mainRoot <> sharedRoots <> publicRoots)
122 -- printDebug (rPrefix "treeParent") ret
124 toTree $ toSubtreeParent r (mainRoot <> sharedRoots <> publicRoots)
126 -- | Fetch only first level of tree
127 tree_first_level :: (HasTreeError err, HasNodeError err)
130 -> Cmd err (Tree NodeTree)
131 tree_first_level r nodeTypes = do
132 -- let rPrefix s = mconcat [ "[tree_first_level] root = "
134 -- , ", nodeTypes = "
138 mainRoot <- findNodes r Private nodeTypes
139 -- printDebug (rPrefix "mainRoot") mainRoot
140 publicRoots <- findNodes r PublicDirect nodeTypes
141 -- printDebug (rPrefix "publicRoots") publicRoots
142 sharedRoots <- findNodes r SharedDirect nodeTypes
143 -- printDebug (rPrefix "sharedRoots") sharedRoots
144 ret <- toTree $ toSubtreeParent r (mainRoot <> sharedRoots <> publicRoots)
145 -- printDebug (rPrefix "tree") ret
148 -- | Fetch tree in a flattened form
149 tree_flat :: (HasTreeError err, HasNodeError err)
153 -> Cmd err [NodeTree]
154 tree_flat r nodeTypes q = do
155 mainRoot <- findNodes r Private nodeTypes
156 publicRoots <- findNodes r PublicDirect nodeTypes
157 sharedRoots <- findNodes r SharedDirect nodeTypes
158 let ret = map toNodeTree (mainRoot <> sharedRoots <> publicRoots)
160 Just v -> pure $ filter (\(NodeTree {_nt_name}) -> Text.isInfixOf (Text.toLower v) (Text.toLower _nt_name)) ret
161 Nothing -> pure $ ret
164 ------------------------------------------------------------------------
165 data NodeMode = Private | Shared | Public | SharedDirect | PublicDirect
167 findNodes :: (HasTreeError err, HasNodeError err)
171 -> Cmd err [DbTreeNode]
172 findNodes r Private nt = dbTree r nt
173 findNodes r Shared nt = findShared r NodeFolderShared nt sharedTreeUpdate
174 findNodes r SharedDirect nt = findSharedDirect r NodeFolderShared nt sharedTreeUpdate
175 findNodes r Public nt = findShared r NodeFolderPublic nt publicTreeUpdate
176 findNodes r PublicDirect nt = findSharedDirect r NodeFolderPublic nt publicTreeUpdate
178 ------------------------------------------------------------------------
179 -- | Collaborative Nodes in the Tree
180 -- Queries the `nodes_nodes` table.
181 findShared :: HasTreeError err
182 => RootId -> NodeType -> [NodeType] -> UpdateTree err
183 -> Cmd err [DbTreeNode]
184 findShared r nt nts fun = do
185 foldersSharedId <- findNodesId r [nt]
186 trees <- mapM (updateTree nts fun) foldersSharedId
189 -- | Find shared folders with "direct" access, i.e. when fetching only
190 -- first-level subcomponents. This works in a simplified manner: fetch the node
191 -- and get the tree for its parent.
192 findSharedDirect :: (HasTreeError err, HasNodeError err)
193 => RootId -> NodeType -> [NodeType] -> UpdateTree err
194 -> Cmd err [DbTreeNode]
195 findSharedDirect r nt nts fun = do
196 -- let rPrefix s = mconcat [ "[findSharedDirect] r = "
204 parent <- getNodeWith r (Proxy :: Proxy HyperdataAny)
205 let mParent = _node_parent_id parent
209 foldersSharedId <- findNodesId parentId [nt]
210 -- printDebug (rPrefix "foldersSharedId") foldersSharedId
211 trees <- mapM (updateTree nts fun) foldersSharedId
212 -- printDebug (rPrefix "trees") trees
216 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
218 updateTree :: HasTreeError err
219 => [NodeType] -> UpdateTree err -> RootId
220 -> Cmd err [DbTreeNode]
221 updateTree nts fun r = do
222 folders <- getNodeNode r
223 nodesSharedId <- mapM (fun r nts)
224 $ map _nn_node2_id folders
225 pure $ concat nodesSharedId
228 sharedTreeUpdate :: HasTreeError err => UpdateTree err
229 sharedTreeUpdate p nt n = dbTree n nt
230 <&> map (\n' -> if (view dt_nodeId n') == n
231 -- && elem (fromDBid $ _dt_typeId n') [NodeGraph]
232 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
233 then set dt_parentId (Just p) n'
236 publicTreeUpdate :: HasTreeError err => UpdateTree err
237 publicTreeUpdate p nt n = dbTree n nt
238 <&> map (\n' -> if _dt_nodeId n' == n
239 -- && (fromDBid $ _dt_typeId n') /= NodeGraph
240 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
241 then set dt_parentId (Just p) n'
246 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
247 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
248 findNodesId r nt = tail
252 findNodesWithType :: RootId -> [NodeType] -> [NodeType] -> Cmd err [DbTreeNode]
253 findNodesWithType root target through =
254 filter isInTarget <$> dbTree root through
256 isInTarget n = List.elem (fromDBid $ view dt_typeId n)
257 $ List.nub $ target <> through
259 ------------------------------------------------------------------------
260 ------------------------------------------------------------------------
261 toTree :: ( MonadError e m
264 => Map (Maybe ParentId) [DbTreeNode]
267 case lookup Nothing m of
268 Just [root] -> pure $ toTree' m root
269 Nothing -> treeError NoRoot
270 Just [] -> treeError EmptyRoot
271 Just _r -> treeError TooManyRoots
274 toTree' :: Map (Maybe ParentId) [DbTreeNode]
278 TreeN (toNodeTree root) $
279 -- Lines below are equivalent computationally but not semantically
280 -- m' ^.. at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')
281 toListOf (at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')) m'
283 toNodeTree :: DbTreeNode
285 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
287 ------------------------------------------------------------------------
288 toTreeParent :: [DbTreeNode]
289 -> Map (Maybe ParentId) [DbTreeNode]
290 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
291 ------------------------------------------------------------------------
292 -- toSubtreeParent' :: [DbTreeNode]
293 -- -> Map (Maybe ParentId) [DbTreeNode]
294 -- toSubtreeParent' ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
296 -- nodeIds = Set.fromList $ map (\n -> unNodeId $ _dt_nodeId n) ns
297 -- nullifiedParents = map nullifyParent ns
298 -- nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
299 -- nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
300 -- , _dt_parentId = Just pId
301 -- , _dt_typeId = tId
302 -- , _dt_name = name }) =
303 -- if Set.member (unNodeId pId) nodeIds then
306 -- DbTreeNode { _dt_nodeId = nId
307 -- , _dt_typeId = tId
308 -- , _dt_parentId = Nothing
309 -- , _dt_name = name }
310 ------------------------------------------------------------------------
311 toSubtreeParent :: RootId
313 -> Map (Maybe ParentId) [DbTreeNode]
314 toSubtreeParent r ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
316 nullifiedParents = map nullifyParent ns
317 nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
318 nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
319 , _dt_parentId = _pId
321 , _dt_name = name }) =
323 DbTreeNode { _dt_nodeId = nId
325 , _dt_parentId = Nothing
329 ------------------------------------------------------------------------
330 -- | Main DB Tree function
333 -> Cmd err [DbTreeNode]
334 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
335 <$> runPGSQuery [sql|
337 tree (id, typename, parent_id, name) AS
339 SELECT p.id, p.typename, p.parent_id, p.name
345 SELECT c.id, c.typename, c.parent_id, c.name
348 INNER JOIN tree AS s ON c.parent_id = s.id
349 WHERE c.typename IN ?
352 |] (rootId, In typename)
354 typename = map nodeTypeId ns
355 ns = case nodeTypes of
359 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
360 isDescendantOf childId rootId = (== [Only True])
361 <$> runPGSQuery [sql|
363 SET TRANSACTION READ ONLY;
367 tree (id, parent_id) AS
369 SELECT c.id, c.parent_id
375 SELECT p.id, p.parent_id
377 INNER JOIN tree AS t ON t.parent_id = p.id
380 SELECT COUNT(*) = 1 from tree AS t
384 -- TODO should we check the category?
385 isIn :: NodeId -> DocId -> Cmd err Bool
386 isIn cId docId = ( == [Only True])
387 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
389 WHERE nn.node1_id = ?
392 -----------------------------------------------------