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 (view, 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 r Private nodeTypes
97 sharedRoots <- findNodes r Shared nodeTypes
98 publicRoots <- findNodes r Public nodeTypes
99 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
101 ------------------------------------------------------------------------
102 data NodeMode = Private | Shared | Public
104 findNodes :: HasTreeError err
108 -> Cmd err [DbTreeNode]
109 findNodes r Private nt = dbTree r nt
110 findNodes r Shared nt = findShared r NodeFolderShared nt sharedTreeUpdate
111 findNodes r Public 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
124 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
126 updateTree :: HasTreeError err
127 => [NodeType] -> UpdateTree err -> RootId
128 -> Cmd err [DbTreeNode]
129 updateTree nts fun r = do
130 folders <- getNodeNode r
131 nodesSharedId <- mapM (fun r nts)
132 $ map _nn_node2_id folders
133 pure $ concat nodesSharedId
136 sharedTreeUpdate :: HasTreeError err => UpdateTree err
137 sharedTreeUpdate p nt n = dbTree n nt
138 <&> map (\n' -> if (view dt_nodeId n') == n
139 -- && elem (fromNodeTypeId $ _dt_typeId n') [NodeGraph]
140 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
141 then set dt_parentId (Just p) n'
144 publicTreeUpdate :: HasTreeError err => UpdateTree err
145 publicTreeUpdate p nt n = dbTree n nt
146 <&> map (\n' -> if _dt_nodeId n' == n
147 -- && (fromNodeTypeId $ _dt_typeId n') /= NodeGraph
148 -- && not (elem (fromNodeTypeId $ _dt_typeId n') [NodeFile])
149 then set dt_parentId (Just p) n'
154 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
155 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
156 findNodesId r nt = tail
159 ------------------------------------------------------------------------
160 ------------------------------------------------------------------------
161 toTree :: ( MonadError e m
163 => Map (Maybe ParentId) [DbTreeNode]
166 case lookup Nothing m of
167 Just [n] -> pure $ toTree' m n
168 Nothing -> treeError NoRoot
169 Just [] -> treeError EmptyRoot
170 Just _ -> treeError TooManyRoots
173 toTree' :: Map (Maybe ParentId) [DbTreeNode]
177 TreeN (toNodeTree n) $
178 -- | Lines below are equivalent computationally but not semantically
179 -- m' ^.. at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')
180 toListOf (at (Just $ _dt_nodeId n) . _Just . each . to (toTree' m')) m'
182 toNodeTree :: DbTreeNode
184 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
186 ------------------------------------------------------------------------
187 toTreeParent :: [DbTreeNode]
188 -> Map (Maybe ParentId) [DbTreeNode]
189 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
190 ------------------------------------------------------------------------
191 -- | Main DB Tree function
194 -> Cmd err [DbTreeNode]
195 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
196 <$> runPGSQuery [sql|
198 tree (id, typename, parent_id, name) AS
200 SELECT p.id, p.typename, p.parent_id, p.name
206 SELECT c.id, c.typename, c.parent_id, c.name
209 INNER JOIN tree AS s ON c.parent_id = s.id
210 WHERE c.typename IN ?
213 |] (rootId, In typename)
215 typename = map nodeTypeId ns
216 ns = case nodeTypes of
220 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
221 isDescendantOf childId rootId = (== [Only True])
222 <$> runPGSQuery [sql|
224 SET TRANSACTION READ ONLY;
228 tree (id, parent_id) AS
230 SELECT c.id, c.parent_id
236 SELECT p.id, p.parent_id
238 INNER JOIN tree AS t ON t.parent_id = p.id
241 SELECT COUNT(*) = 1 from tree AS t
245 -- TODO should we check the category?
246 isIn :: NodeId -> DocId -> Cmd err Bool
247 isIn cId docId = ( == [Only True])
248 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
250 WHERE nn.node1_id = ?
253 -----------------------------------------------------