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
38 import Control.Lens (view, toListOf, at, each, _Just, to, set, makeLenses)
39 import Control.Monad.Error.Class (MonadError())
40 import Data.List (tail, concat, nub)
41 import Data.Map (Map, fromListWith, lookup)
42 import qualified Data.Set as Set
43 import qualified Data.List as List
44 import Data.Text (Text)
45 import Database.PostgreSQL.Simple
46 import Database.PostgreSQL.Simple.SqlQQ
48 import Gargantext.Prelude
49 import Gargantext.Core
50 import Gargantext.Core.Types.Main (NodeTree(..), Tree(..))
51 import Gargantext.Database.Admin.Config hiding (nodeTypes)
52 import Gargantext.Database.Admin.Types.Node
53 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
54 import Gargantext.Database.Query.Table.NodeNode (getNodeNode)
55 import Gargantext.Database.Query.Tree.Error
56 import Gargantext.Database.Schema.NodeNode (NodeNodePoly(..))
58 ------------------------------------------------------------------------
59 data DbTreeNode = DbTreeNode { _dt_nodeId :: NodeId
61 , _dt_parentId :: Maybe NodeId
65 makeLenses ''DbTreeNode
67 instance Eq DbTreeNode where
68 (==) d1 d2 = (==) (_dt_nodeId d1) (_dt_nodeId d2)
70 ------------------------------------------------------------------------
72 data TreeMode = TreeBasic | TreeAdvanced | TreeFirstLevel
74 -- | Returns the Tree of Nodes in Database
75 tree :: HasTreeError err
79 -> Cmd err (Tree NodeTree)
80 tree TreeBasic = tree_basic
81 tree TreeAdvanced = tree_advanced
82 tree TreeFirstLevel = tree_first_level
84 -- | Tree basic returns the Tree of Nodes in Database
85 -- (without shared folders)
86 -- keeping this for teaching purpose only
87 tree_basic :: HasTreeError err
90 -> Cmd err (Tree NodeTree)
91 tree_basic r nodeTypes =
92 (dbTree r nodeTypes <&> toTreeParent) >>= toTree
93 -- Same as (but easier to read) :
94 -- toTree =<< (toTreeParent <$> dbTree r nodeTypes)
96 -- | Advanced mode of the Tree enables shared nodes
97 tree_advanced :: HasTreeError err
100 -> Cmd err (Tree NodeTree)
101 tree_advanced r nodeTypes = do
102 mainRoot <- findNodes r Private nodeTypes
103 sharedRoots <- findNodes r Shared nodeTypes
104 publicRoots <- findNodes r Public nodeTypes
105 toTree $ toTreeParent (mainRoot <> sharedRoots <> publicRoots)
107 -- | Fetch only first level of tree
108 tree_first_level :: HasTreeError err
111 -> Cmd err (Tree NodeTree)
112 tree_first_level r nodeTypes = do
113 mainRoot <- findNodes r Private nodeTypes
114 sharedRoots <- findNodes r Shared nodeTypes
115 publicRoots <- findNodes r Public nodeTypes
116 toTree $ toSubtreeParent (mainRoot <> sharedRoots <> publicRoots)
118 ------------------------------------------------------------------------
119 data NodeMode = Private | Shared | Public
121 findNodes :: HasTreeError err
125 -> Cmd err [DbTreeNode]
126 findNodes r Private nt = dbTree r nt
127 findNodes r Shared nt = findShared r NodeFolderShared nt sharedTreeUpdate
128 findNodes r Public nt = findShared r NodeFolderPublic nt publicTreeUpdate
130 ------------------------------------------------------------------------
131 -- | Collaborative Nodes in the Tree
132 findShared :: HasTreeError err
133 => RootId -> NodeType -> [NodeType] -> UpdateTree err
134 -> Cmd err [DbTreeNode]
135 findShared r nt nts fun = do
136 foldersSharedId <- findNodesId r [nt]
137 trees <- mapM (updateTree nts fun) foldersSharedId
141 type UpdateTree err = ParentId -> [NodeType] -> NodeId -> Cmd err [DbTreeNode]
143 updateTree :: HasTreeError err
144 => [NodeType] -> UpdateTree err -> RootId
145 -> Cmd err [DbTreeNode]
146 updateTree nts fun r = do
147 folders <- getNodeNode r
148 nodesSharedId <- mapM (fun r nts)
149 $ map _nn_node2_id folders
150 pure $ concat nodesSharedId
153 sharedTreeUpdate :: HasTreeError err => UpdateTree err
154 sharedTreeUpdate p nt n = dbTree n nt
155 <&> map (\n' -> if (view dt_nodeId n') == n
156 -- && elem (fromDBid $ _dt_typeId n') [NodeGraph]
157 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
158 then set dt_parentId (Just p) n'
161 publicTreeUpdate :: HasTreeError err => UpdateTree err
162 publicTreeUpdate p nt n = dbTree n nt
163 <&> map (\n' -> if _dt_nodeId n' == n
164 -- && (fromDBid $ _dt_typeId n') /= NodeGraph
165 -- && not (elem (fromDBid $ _dt_typeId n') [NodeFile])
166 then set dt_parentId (Just p) n'
171 -- | findNodesId returns all nodes matching nodeType but the root (Nodeuser)
172 findNodesId :: RootId -> [NodeType] -> Cmd err [NodeId]
173 findNodesId r nt = tail
177 findNodesWithType :: RootId -> [NodeType] -> [NodeType] -> Cmd err [DbTreeNode]
178 findNodesWithType root target through =
179 filter isInTarget <$> dbTree root through
181 isInTarget n = List.elem (fromDBid $ view dt_typeId n)
182 $ List.nub $ target <> through
184 ------------------------------------------------------------------------
185 ------------------------------------------------------------------------
186 toTree :: ( MonadError e m
189 => Map (Maybe ParentId) [DbTreeNode]
192 case lookup Nothing m of
193 Just [root] -> pure $ toTree' m root
194 Nothing -> treeError NoRoot
195 Just [] -> treeError EmptyRoot
196 Just _r -> treeError TooManyRoots
199 toTree' :: Map (Maybe ParentId) [DbTreeNode]
203 TreeN (toNodeTree root) $
204 -- | Lines below are equivalent computationally but not semantically
205 -- m' ^.. at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')
206 toListOf (at (Just $ _dt_nodeId root) . _Just . each . to (toTree' m')) m'
208 toNodeTree :: DbTreeNode
210 toNodeTree (DbTreeNode nId tId _ n) = NodeTree n (fromNodeTypeId tId) nId
212 ------------------------------------------------------------------------
213 toTreeParent :: [DbTreeNode]
214 -> Map (Maybe ParentId) [DbTreeNode]
215 toTreeParent = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n]))
216 ------------------------------------------------------------------------
217 toSubtreeParent :: [DbTreeNode]
218 -> Map (Maybe ParentId) [DbTreeNode]
219 toSubtreeParent ns = fromListWith (\a b -> nub $ a <> b) . map (\n -> (_dt_parentId n, [n])) $ nullifiedParents
221 nodeIds = Set.fromList $ map (\n -> unNodeId $ _dt_nodeId n) ns
222 nullifiedParents = map nullifyParent ns
223 nullifyParent dt@(DbTreeNode { _dt_parentId = Nothing }) = dt
224 nullifyParent dt@(DbTreeNode { _dt_nodeId = nId
225 , _dt_parentId = Just pId
227 , _dt_name = name }) =
228 if Set.member (unNodeId pId) nodeIds then
231 DbTreeNode { _dt_nodeId = nId
233 , _dt_parentId = Nothing
235 ------------------------------------------------------------------------
236 -- | Main DB Tree function
239 -> Cmd err [DbTreeNode]
240 dbTree rootId nodeTypes = map (\(nId, tId, pId, n) -> DbTreeNode nId tId pId n)
241 <$> runPGSQuery [sql|
243 tree (id, typename, parent_id, name) AS
245 SELECT p.id, p.typename, p.parent_id, p.name
251 SELECT c.id, c.typename, c.parent_id, c.name
254 INNER JOIN tree AS s ON c.parent_id = s.id
255 WHERE c.typename IN ?
258 |] (rootId, In typename)
260 typename = map nodeTypeId ns
261 ns = case nodeTypes of
265 isDescendantOf :: NodeId -> RootId -> Cmd err Bool
266 isDescendantOf childId rootId = (== [Only True])
267 <$> runPGSQuery [sql|
269 SET TRANSACTION READ ONLY;
273 tree (id, parent_id) AS
275 SELECT c.id, c.parent_id
281 SELECT p.id, p.parent_id
283 INNER JOIN tree AS t ON t.parent_id = p.id
286 SELECT COUNT(*) = 1 from tree AS t
290 -- TODO should we check the category?
291 isIn :: NodeId -> DocId -> Cmd err Bool
292 isIn cId docId = ( == [Only True])
293 <$> runPGSQuery [sql| SELECT COUNT(*) = 1
295 WHERE nn.node1_id = ?
298 -----------------------------------------------------