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
37 import Control.Lens ({-(^..)-} toListOf, at, each, _Just, to, set, makeLenses)
38 import Control.Monad.Error.Class (MonadError())
39 import Data.List (tail, concat, nub)
40 import Data.Map (Map, fromListWith, lookup)
41 import Data.Text (Text)
42 import Database.PostgreSQL.Simple
43 import Database.PostgreSQL.Simple.SqlQQ
44 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
45 import Gargantext.Database.Admin.Config (fromNodeTypeId, nodeTypeId, fromNodeTypeId)
46 import Gargantext.Database.Admin.Types.Node
47 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
48 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
49 import Gargantext.Database.Query.Tree.Error
50 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
51 import Gargantext.Prelude
53 ------------------------------------------------------------------------
54 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
56 , _dt_parentId :: Maybe NodeId
60 makeLenses ''DbTreeNode
62 instance Eq DbTreeNode where
63 (==) d1 d2 = (==) (_dt_nodeId d1) (_dt_nodeId d2)
65 ------------------------------------------------------------------------
67 data TreeMode = TreeBasic | TreeAdvanced
69 -- | Returns the Tree of Nodes in Database
70 tree :: HasTreeError err
74 -> Cmd err (Tree NodeTree)
75 tree TreeBasic = tree_basic
76 tree TreeAdvanced = tree_advanced
78 -- | Tree basic returns the Tree of Nodes in Database
79 -- (without shared folders)
80 -- keeping this for teaching purpose only
81 tree_basic :: HasTreeError err
84 -> Cmd err (Tree NodeTree)
85 tree_basic r nodeTypes =
86 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
87 -- Same as (but easier to read) :
88 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
90 -- | Advanced mode of the Tree enables shared nodes
91 tree_advanced :: HasTreeError err
94 -> Cmd err (Tree NodeTree)
95 tree_advanced r nodeTypes = do
96 mainRoot <- findNodes Private r nodeTypes
97 sharedRoots <- findNodes Shared r nodeTypes
98 publicRoots <- findNodes Public r nodeTypes
99 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
101 ------------------------------------------------------------------------
102 data NodeMode = Private | Shared | Public
104 findNodes :: HasTreeError err
106 -> RootId -> [NodeType]
107 -> Cmd err [DbTreeNode]
108 findNodes Private r nt = dbTree r nt
109 findNodes Shared r nt = findShared r NodeFolderShared nt sharedTreeUpdate
110 findNodes Public r nt = findShared r NodeFolderPublic nt publicTreeUpdate
113 ------------------------------------------------------------------------
114 -- | Collaborative Nodes in the Tree
115 findShared :: HasTreeError err
116 => RootId -> NodeType -> [NodeType] -> UpdateTree err
117 -> Cmd err [DbTreeNode]
118 findShared r nt nts fun = do
119 foldersSharedId <- findNodesId r [nt]
120 trees <- mapM (updateTree nts fun) foldersSharedId
123 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
125 updateTree :: HasTreeError err
126 => [NodeType] -> UpdateTree err -> RootId
127 -> Cmd err [DbTreeNode]
128 updateTree nts fun r = do
129 folders <- getNodeNode r
130 nodesSharedId <- mapM (fun r nts)
131 $ map _nn_node2_id folders
132 pure $ concat nodesSharedId
135 sharedTreeUpdate :: HasTreeError err => UpdateTree err
136 sharedTreeUpdate p nt n = dbTree n nt
137 <&> map (\n' -> if _dt_nodeId n' == n
138 -- && elem (fromNodeTypeId $ _dt_typeId n') [NodeGraph]
139 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
140 then set dt_parentId (Just p) n'
143 publicTreeUpdate :: HasTreeError err => UpdateTree err
144 publicTreeUpdate p nt n = dbTree n nt
145 <&> map (\n' -> if _dt_nodeId n' == n
146 -- && (fromNodeTypeId $ _dt_typeId n') /= NodeGraph
147 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
148 then set dt_parentId (Just p) n'
153 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
154 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
155 findNodesId r nt = tail
158 ------------------------------------------------------------------------
159 ------------------------------------------------------------------------
160 toTree :: ( MonadError e m
162 => Map (Maybe ParentId) [DbTreeNode]
165 case lookup Nothing m of
166 Just [n] -> pure $ toTree' m n
167 Nothing -> treeError NoRoot
168 Just [] -> treeError EmptyRoot
169 Just _ -> treeError TooManyRoots
172 toTree' :: Map (Maybe ParentId) [DbTreeNode]
176 TreeN (toNodeTree n) $
177 -- | Lines below are equivalent computationally but not semantically
178 -- m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
179 toListOf (at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')) m'
181 toNodeTree :: DbTreeNode
183 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
185 ------------------------------------------------------------------------
186 toTreeParent :: [DbTreeNode]
187 -> Map (Maybe ParentId) [DbTreeNode]
188 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
189 ------------------------------------------------------------------------
190 -- | Main DB Tree function
193 -> Cmd err [DbTreeNode]
194 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
195 <$> runPGSQuery [sql|
197 tree (id, typename, parent_id, name) AS
199 SELECT p.id, p.typename, p.parent_id, p.name
205 SELECT c.id, c.typename, c.parent_id, c.name
208 INNER JOIN tree AS s ON c.parent_id = s.id
209 WHERE c.typename IN ?
212 |] (rootId, In typename)
214 typename = map nodeTypeId ns
215 ns = case nodeTypes of
219 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
220 isDescendantOf childId rootId = (== [Only True])
221 <$> runPGSQuery [sql|
223 SET TRANSACTION READ ONLY;
227 tree (id, parent_id) AS
229 SELECT c.id, c.parent_id
235 SELECT p.id, p.parent_id
237 INNER JOIN tree AS t ON t.parent_id = p.id
240 SELECT COUNT(*) = 1 from tree AS t
244 -- TODO should we check the category?
245 isIn :: NodeId -> DocId -> Cmd err Bool
246 isIn cId docId = ( == [Only True])
247 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
249 WHERE nn.node1_id = ?
252 -----------------------------------------------------