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
15 {-# LANGUAGE FlexibleContexts #-}
16 {-# LANGUAGE NoImplicitPrelude #-}
17 {-# LANGUAGE OverloadedStrings #-}
18 {-# LANGUAGE QuasiQuotes #-}
19 {-# LANGUAGE RankNTypes #-}
21 module Gargantext.Database.Action.Query.Tree
24 import Control.Lens (Prism', (#), (^..), at, each, _Just, to)
25 import Control.Monad.Error.Class (MonadError(throwError))
26 import Data.Map (Map, fromListWith, lookup)
27 import Data.Text (Text)
28 import Database.PostgreSQL.Simple
29 import Database.PostgreSQL.Simple.SqlQQ
30 import Gargantext.Core.Types.Individu
31 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
32 import Gargantext.Database.Admin.Types.Node -- (pgNodeId, NodeType(..))
33 import Gargantext.Database.Action.Query
34 import Gargantext.Database.Action.Flow.Utils (getUserId)
35 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId)
36 import Gargantext.Database.Admin.Types.Errors
37 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType, DocId, allNodeTypes)
38 import Gargantext.Database.Admin.Utils (Cmd, runPGSQuery)
39 import Gargantext.Prelude
41 ------------------------------------------------------------------------
42 -- import Gargantext.Database.Utils (runCmdDev)
43 -- treeTest :: IO (Tree NodeTree)
44 -- treeTest = runCmdDev $ treeDB 347474
45 ------------------------------------------------------------------------
47 mkRoot :: HasNodeError err
57 False -> nodeError NegativeId
59 rs <- mkNodeWithParent NodeUser Nothing uid una
62 _ <- mkNodeWithParent NodeFolderPrivate (Just r) uid una
63 _ <- mkNodeWithParent NodeFolderShared (Just r) uid una
64 _ <- mkNodeWithParent NodeFolderPublic (Just r) uid una
70 ------------------------------------------------------------------------
71 data TreeError = NoRoot | EmptyRoot | TooManyRoots
74 class HasTreeError e where
75 _TreeError :: Prism' e TreeError
77 treeError :: (MonadError e m, HasTreeError e) => TreeError -> m a
78 treeError te = throwError $ _TreeError # te
80 -- | Returns the Tree of Nodes in Database
81 treeDB :: HasTreeError err => RootId -> [NodeType] -> Cmd err (Tree NodeTree)
82 treeDB r nodeTypes = toTree =<< (toTreeParent <$> dbTree r nodeTypes)
84 ------------------------------------------------------------------------
85 toTree :: (MonadError e m, HasTreeError e)
86 => Map (Maybe ParentId) [DbTreeNode] -> m (Tree NodeTree)
88 case lookup Nothing m of
89 Just [n] -> pure $ toTree' m n
90 Nothing -> treeError NoRoot
91 Just [] -> treeError EmptyRoot
92 Just _ -> treeError TooManyRoots
94 toTree' :: Map (Maybe ParentId) [DbTreeNode] -> DbTreeNode -> Tree NodeTree
96 TreeN (toNodeTree n) $
97 m ^.. at (Just $ dt_nodeId n) . _Just . each . to (toTree' m)
99 ------------------------------------------------------------------------
100 toNodeTree :: DbTreeNode -> NodeTree
101 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
103 nodeType = fromNodeTypeId tId
104 ------------------------------------------------------------------------
105 toTreeParent :: [DbTreeNode] -> Map (Maybe ParentId) [DbTreeNode]
106 toTreeParent = fromListWith (<>) . map (\n -> (dt_parentId n, [n]))
107 ------------------------------------------------------------------------
108 data DbTreeNode = DbTreeNode { dt_nodeId :: NodeId
110 , dt_parentId :: Maybe NodeId
114 -- | Main DB Tree function
115 -- TODO add typenames as parameters
116 dbTree :: RootId -> [NodeType] -> Cmd err [DbTreeNode]
117 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
118 <$> runPGSQuery [sql|
120 tree (id, typename, parent_id, name) AS
122 SELECT p.id, p.typename, p.parent_id, p.name
128 SELECT c.id, c.typename, c.parent_id, c.name
131 INNER JOIN tree AS s ON c.parent_id = s.id
132 WHERE c.typename IN ?
135 |] (rootId, In typename)
137 typename = map nodeTypeId ns
138 ns = case nodeTypes of
140 -- [2, 20, 21, 22, 3, 5, 30, 31, 40, 7, 9, 90, 71]
143 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
144 isDescendantOf childId rootId = (== [Only True])
145 <$> runPGSQuery [sql|
147 SET TRANSACTION READ ONLY;
151 tree (id, parent_id) AS
153 SELECT c.id, c.parent_id
159 SELECT p.id, p.parent_id
161 INNER JOIN tree AS t ON t.parent_id = p.id
164 SELECT COUNT(*) = 1 from tree AS t
168 -- TODO should we check the category?
169 isIn :: NodeId -> DocId -> Cmd err Bool
170 isIn cId docId = ( == [Only True])
171 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
173 WHERE nn.node1_id = ?