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.Admin.Config (fromNodeTypeId, nodeTypeId)
41 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType, DocId, allNodeTypes)
42 import Gargantext.Database.Admin.Types.Errors
43 import Gargantext.Database.Admin.Utils (Cmd, runPGSQuery)
44 import Gargantext.Database.Action.Query.Node
45 import Gargantext.Database.Action.Query.User
47 ------------------------------------------------------------------------
48 -- import Gargantext.Database.Utils (runCmdDev)
49 -- treeTest :: IO (Tree NodeTree)
50 -- treeTest = runCmdDev $ treeDB 347474
51 ------------------------------------------------------------------------
53 mkRoot :: HasNodeError err
63 False -> nodeError NegativeId
65 rs <- mkNodeWithParent NodeUser Nothing uid una
68 _ <- mkNodeWithParent NodeFolderPrivate (Just r) uid una
69 _ <- mkNodeWithParent NodeFolderShared (Just r) uid una
70 _ <- mkNodeWithParent NodeFolderPublic (Just r) uid una
76 ------------------------------------------------------------------------
77 data TreeError = NoRoot | EmptyRoot | TooManyRoots
80 class HasTreeError e where
81 _TreeError :: Prism' e TreeError
83 treeError :: (MonadError e m, HasTreeError e) => TreeError -> m a
84 treeError te = throwError $ _TreeError # te
86 -- | Returns the Tree of Nodes in Database
87 treeDB :: HasTreeError err => RootId -> [NodeType] -> Cmd err (Tree NodeTree)
88 treeDB r nodeTypes = toTree =<< (toTreeParent <$> dbTree r nodeTypes)
91 type ParentId = NodeId
92 ------------------------------------------------------------------------
93 toTree :: (MonadError e m, HasTreeError e)
94 => Map (Maybe ParentId) [DbTreeNode] -> m (Tree NodeTree)
96 case lookup Nothing m of
97 Just [n] -> pure $ toTree' m n
98 Nothing -> treeError NoRoot
99 Just [] -> treeError EmptyRoot
100 Just _ -> treeError TooManyRoots
102 toTree' :: Map (Maybe ParentId) [DbTreeNode] -> DbTreeNode -> Tree NodeTree
104 TreeN (toNodeTree n) $
105 m ^.. at (Just $ dt_nodeId n) . _Just . each . to (toTree' m)
107 ------------------------------------------------------------------------
108 toNodeTree :: DbTreeNode -> NodeTree
109 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n nodeType nId
111 nodeType = fromNodeTypeId tId
112 ------------------------------------------------------------------------
113 toTreeParent :: [DbTreeNode] -> Map (Maybe ParentId) [DbTreeNode]
114 toTreeParent = fromListWith (<>) . map (\n -> (dt_parentId n, [n]))
115 ------------------------------------------------------------------------
116 data DbTreeNode = DbTreeNode { dt_nodeId :: NodeId
118 , dt_parentId :: Maybe NodeId
122 -- | Main DB Tree function
123 -- TODO add typenames as parameters
124 dbTree :: RootId -> [NodeType] -> Cmd err [DbTreeNode]
125 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
126 <$> runPGSQuery [sql|
128 tree (id, typename, parent_id, name) AS
130 SELECT p.id, p.typename, p.parent_id, p.name
136 SELECT c.id, c.typename, c.parent_id, c.name
139 INNER JOIN tree AS s ON c.parent_id = s.id
140 WHERE c.typename IN ?
143 |] (rootId, In typename)
145 typename = map nodeTypeId ns
146 ns = case nodeTypes of
148 -- [2, 20, 21, 22, 3, 5, 30, 31, 40, 7, 9, 90, 71]
151 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
152 isDescendantOf childId rootId = (== [Only True])
153 <$> runPGSQuery [sql|
155 SET TRANSACTION READ ONLY;
159 tree (id, parent_id) AS
161 SELECT c.id, c.parent_id
167 SELECT p.id, p.parent_id
169 INNER JOIN tree AS t ON t.parent_id = p.id
172 SELECT COUNT(*) = 1 from tree AS t
176 -- TODO should we check the category?
177 isIn :: NodeId -> DocId -> Cmd err Bool
178 isIn cId docId = ( == [Only True])
179 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
181 WHERE nn.node1_id = ?