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 QuasiQuotes #-}
18 {-# LANGUAGE RankNTypes #-}
20 module Gargantext.Database.Action.Query.Tree
31 import Control.Lens (Prism', (#), (^..), at, each, _Just, to)
32 import Control.Monad.Error.Class (MonadError(throwError))
33 import Data.Map (Map, fromListWith, lookup)
34 import Data.Text (Text)
35 import Database.PostgreSQL.Simple
36 import Database.PostgreSQL.Simple.SqlQQ
38 import Gargantext.Prelude
39 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
40 import Gargantext.Database.Config (fromNodeTypeId, nodeTypeId)
41 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType, DocId, allNodeTypes)
42 import Gargantext.Database.Admin.Utils (Cmd, runPGSQuery)
43 import Gargantext.Database.Admin.Tools.Node
44 import Gargantext.Database.Admin.Tools.User
46 ------------------------------------------------------------------------
47 -- import Gargantext.Database.Utils (runCmdDev)
48 -- treeTest :: IO (Tree NodeTree)
49 -- treeTest = runCmdDev $ treeDB 347474
50 ------------------------------------------------------------------------
52 mkRoot :: HasNodeError err
62 False -> nodeError NegativeId
64 rs <- mkNodeWithParent NodeUser Nothing uid una
67 _ <- mkNodeWithParent NodeFolderPrivate (Just r) uid una
68 _ <- mkNodeWithParent NodeFolderShared (Just r) uid una
69 _ <- mkNodeWithParent NodeFolderPublic (Just r) uid una
75 ------------------------------------------------------------------------
76 data TreeError = NoRoot | EmptyRoot | TooManyRoots
79 class HasTreeError e where
80 _TreeError :: Prism' e TreeError
82 treeError :: (MonadError e m, HasTreeError e) => TreeError -> m a
83 treeError te = throwError $ _TreeError # te
85 -- | Returns the Tree of Nodes in Database
86 treeDB :: HasTreeError err => RootId -> [NodeType] -> Cmd err (Tree NodeTree)
87 treeDB r nodeTypes = toTree =<< (toTreeParent <$> dbTree r nodeTypes)
90 type ParentId = NodeId
91 ------------------------------------------------------------------------
92 toTree :: (MonadError e m, HasTreeError e)
93 => Map (Maybe ParentId) [DbTreeNode] -> m (Tree NodeTree)
95 case lookup Nothing m of
96 Just [n] -> pure $ toTree' m n
97 Nothing -> treeError NoRoot
98 Just [] -> treeError EmptyRoot
99 Just _ -> treeError TooManyRoots
101 toTree' :: Map (Maybe ParentId) [DbTreeNode] -> DbTreeNode -> Tree NodeTree
103 TreeN (toNodeTree n) $
104 m ^.. at (Just $ dt_nodeId n) . _Just . each . to (toTree' m)
106 ------------------------------------------------------------------------
107 toNodeTree :: DbTreeNode -> NodeTree
108 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
110 nodeType = fromNodeTypeId tId
111 ------------------------------------------------------------------------
112 toTreeParent :: [DbTreeNode] -> Map (Maybe ParentId) [DbTreeNode]
113 toTreeParent = fromListWith (<>) . map (\n -> (dt_parentId n, [n]))
114 ------------------------------------------------------------------------
115 data DbTreeNode = DbTreeNode { dt_nodeId :: NodeId
117 , dt_parentId :: Maybe NodeId
121 -- | Main DB Tree function
122 -- TODO add typenames as parameters
123 dbTree :: RootId -> [NodeType] -> Cmd err [DbTreeNode]
124 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
125 <$> runPGSQuery [sql|
127 tree (id, typename, parent_id, name) AS
129 SELECT p.id, p.typename, p.parent_id, p.name
135 SELECT c.id, c.typename, c.parent_id, c.name
138 INNER JOIN tree AS s ON c.parent_id = s.id
139 WHERE c.typename IN ?
142 |] (rootId, In typename)
144 typename = map nodeTypeId ns
145 ns = case nodeTypes of
147 -- [2, 20, 21, 22, 3, 5, 30, 31, 40, 7, 9, 90, 71]
150 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
151 isDescendantOf childId rootId = (== [Only True])
152 <$> runPGSQuery [sql|
154 SET TRANSACTION READ ONLY;
158 tree (id, parent_id) AS
160 SELECT c.id, c.parent_id
166 SELECT p.id, p.parent_id
168 INNER JOIN tree AS t ON t.parent_id = p.id
171 SELECT COUNT(*) = 1 from tree AS t
175 -- TODO should we check the category?
176 isIn :: NodeId -> DocId -> Cmd err Bool
177 isIn cId docId = ( == [Only True])
178 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
180 WHERE nn.node1_id = ?