2 Module : Gargantext.Database.Schema.Node
3 Description : Main requests of Node to the database
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
11 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
12 {-# OPTIONS_GHC -fno-warn-orphans #-}
14 {-# LANGUAGE Arrows #-}
15 {-# LANGUAGE DeriveGeneric #-}
16 {-# LANGUAGE ConstraintKinds #-}
17 {-# LANGUAGE FlexibleContexts #-}
18 {-# LANGUAGE FlexibleInstances #-}
19 {-# LANGUAGE FunctionalDependencies #-}
20 {-# LANGUAGE OverloadedStrings #-}
21 {-# LANGUAGE MultiParamTypeClasses #-}
22 {-# LANGUAGE NoImplicitPrelude #-}
23 {-# LANGUAGE RankNTypes #-}
24 {-# LANGUAGE TemplateHaskell #-}
26 module Gargantext.Database.Schema.Node where
28 import Control.Arrow (returnA)
29 import Control.Lens (Prism', set, view, (#), (^?))
30 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
31 import Control.Monad.Error.Class (MonadError(..))
33 import Data.Maybe (Maybe(..), fromMaybe)
34 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
35 import Data.Text (Text, pack)
36 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
37 import GHC.Int (Int64)
38 import Gargantext.Core (Lang(..))
39 import Gargantext.Core.Types
40 import Gargantext.Core.Types.Individu (Username)
41 import Gargantext.Core.Types.Main (UserId)
42 import Gargantext.Database.Config (nodeTypeId)
43 import Gargantext.Database.Queries.Filter (limit', offset')
44 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
45 import Gargantext.Database.Utils
46 import Gargantext.Prelude hiding (sum, head)
47 import Opaleye hiding (FromField)
48 import Opaleye.Internal.QueryArr (Query)
49 import Prelude hiding (null, id, map, sum)
51 ------------------------------------------------------------------------
53 data NodeError = NoListFound
66 class HasNodeError e where
67 _NodeError :: Prism' e NodeError
69 nodeError :: (MonadError e m, HasNodeError e) => NodeError -> m a
70 nodeError ne = throwError $ _NodeError # ne
72 catchNodeError :: (MonadError e m, HasNodeError e) => m a -> (NodeError -> m a) -> m a
73 catchNodeError f g = catchError f (\e -> maybe (throwError e) g (e ^? _NodeError))
75 ------------------------------------------------------------------------
76 instance FromField HyperdataAny where
77 fromField = fromField'
79 instance FromField HyperdataCorpus
81 fromField = fromField'
83 instance FromField HyperdataDocument
85 fromField = fromField'
87 instance FromField HyperdataDocumentV3
89 fromField = fromField'
91 instance FromField HyperdataUser
93 fromField = fromField'
95 instance FromField HyperdataList
97 fromField = fromField'
99 instance FromField HyperdataGraph
101 fromField = fromField'
103 instance FromField HyperdataAnnuaire
105 fromField = fromField'
106 ------------------------------------------------------------------------
107 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
109 queryRunnerColumnDefault = fieldQueryRunnerColumn
111 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
113 queryRunnerColumnDefault = fieldQueryRunnerColumn
115 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
117 queryRunnerColumnDefault = fieldQueryRunnerColumn
119 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
121 queryRunnerColumnDefault = fieldQueryRunnerColumn
123 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
125 queryRunnerColumnDefault = fieldQueryRunnerColumn
127 instance QueryRunnerColumnDefault PGJsonb HyperdataList
129 queryRunnerColumnDefault = fieldQueryRunnerColumn
131 instance QueryRunnerColumnDefault PGJsonb HyperdataGraph
133 queryRunnerColumnDefault = fieldQueryRunnerColumn
135 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
137 queryRunnerColumnDefault = fieldQueryRunnerColumn
139 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
141 queryRunnerColumnDefault = fieldQueryRunnerColumn
143 instance QueryRunnerColumnDefault PGInt4 (Maybe NodeParentId)
145 queryRunnerColumnDefault = fieldQueryRunnerColumn
147 ------------------------------------------------------------------------
149 -- TODO Classe HasDefault where
150 -- default NodeType = Hyperdata
151 ------------------------------------------------------------------------
152 $(makeAdaptorAndInstance "pNode" ''NodePoly)
153 $(makeLensesWith abbreviatedFields ''NodePoly)
154 $(makeAdaptorAndInstance "pNodeSearch" ''NodePolySearch)
155 $(makeLensesWith abbreviatedFields ''NodePolySearch)
157 type NodeWrite = NodePoly (Maybe (Column PGInt4 ))
160 (Maybe (Column PGInt4 ))
162 (Maybe (Column PGTimestamptz))
165 type NodeRead = NodePoly (Column PGInt4 )
170 (Column PGTimestamptz )
173 type NodeReadNull = NodePoly (Column (Nullable PGInt4 ))
174 (Column (Nullable PGInt4 ))
175 (Column (Nullable PGInt4 ))
176 (Column (Nullable PGInt4 ))
177 (Column (Nullable PGText ))
178 (Column (Nullable PGTimestamptz ))
179 (Column (Nullable PGJsonb))
181 nodeTable :: Table NodeWrite NodeRead
182 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
183 , _node_typename = required "typename"
184 , _node_userId = required "user_id"
186 , _node_parentId = optional "parent_id"
187 , _node_name = required "name"
188 , _node_date = optional "date"
190 , _node_hyperdata = required "hyperdata"
194 queryNodeTable :: Query NodeRead
195 queryNodeTable = queryTable nodeTable
197 ------------------------------------------------------------------------
198 -- | Node(Read|Write)Search is slower than Node(Write|Read) use it
199 -- for full text search only
200 type NodeSearchWrite = NodePolySearch (Maybe (Column PGInt4 ))
203 (Column (Nullable PGInt4 ))
205 (Maybe (Column PGTimestamptz))
207 (Maybe (Column PGTSVector))
209 type NodeSearchRead = NodePolySearch (Column PGInt4 )
212 (Column (Nullable PGInt4 ))
214 (Column PGTimestamptz )
218 type NodeSearchReadNull = NodePolySearch (Column (Nullable PGInt4 ))
219 (Column (Nullable PGInt4 ))
220 (Column (Nullable PGInt4 ))
221 (Column (Nullable PGInt4 ))
222 (Column (Nullable PGText ))
223 (Column (Nullable PGTimestamptz ))
224 (Column (Nullable PGJsonb))
225 (Column (Nullable PGTSVector))
228 nodeTableSearch :: Table NodeSearchWrite NodeSearchRead
229 nodeTableSearch = Table "nodes" (pNodeSearch NodeSearch { _ns_id = optional "id"
230 , _ns_typename = required "typename"
231 , _ns_userId = required "user_id"
233 , _ns_parentId = required "parent_id"
234 , _ns_name = required "name"
235 , _ns_date = optional "date"
237 , _ns_hyperdata = required "hyperdata"
238 , _ns_search = optional "search"
243 queryNodeSearchTable :: Query NodeSearchRead
244 queryNodeSearchTable = queryTable nodeTableSearch
246 selectNode :: Column PGInt4 -> Query NodeRead
247 selectNode id = proc () -> do
248 row <- queryNodeTable -< ()
249 restrict -< _node_id row .== id
252 runGetNodes :: Query NodeRead -> Cmd err [NodeAny]
253 runGetNodes = runOpaQuery
255 ------------------------------------------------------------------------
256 ------------------------------------------------------------------------
258 -- | order by publication date
259 -- Favorites (Bool), node_ngrams
260 selectNodesWith :: ParentId -> Maybe NodeType
261 -> Maybe Offset -> Maybe Limit -> Query NodeRead
262 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
263 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
264 limit' maybeLimit $ offset' maybeOffset
265 $ orderBy (asc _node_id)
266 $ selectNodesWith' parentId maybeNodeType
268 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
269 selectNodesWith' parentId maybeNodeType = proc () -> do
270 node <- (proc () -> do
271 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
272 restrict -< parentId' .== (pgInt4 parentId)
274 let typeId' = maybe 0 nodeTypeId maybeNodeType
276 restrict -< if typeId' > 0
277 then typeId .== (pgInt4 (typeId' :: Int))
279 returnA -< row ) -< ()
283 deleteNode :: Int -> Cmd err Int
284 deleteNode n = mkCmd $ \conn ->
285 fromIntegral <$> runDelete conn nodeTable
286 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
288 deleteNodes :: [Int] -> Cmd err Int
289 deleteNodes ns = mkCmd $ \conn ->
290 fromIntegral <$> runDelete conn nodeTable
291 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
293 -- TODO: NodeType should match with `a'
294 getNodesWith :: JSONB a => Int -> proxy a -> Maybe NodeType
295 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
296 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
297 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
299 -- TODO: Why is the second parameter ignored?
300 -- TODO: Why not use getNodesWith?
301 getNodesWithParentId :: Int -> Maybe Text -> Cmd err [NodeAny]
302 getNodesWithParentId n _ = runOpaQuery $ selectNodesWithParentID n
304 ------------------------------------------------------------------------
305 getDocumentsV3WithParentId :: Int -> Cmd err [Node HyperdataDocumentV3]
306 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
308 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
309 getDocumentsWithParentId :: Int -> Cmd err [Node HyperdataDocument]
310 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
312 getListsWithParentId :: Int -> Cmd err [Node HyperdataList]
313 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)
315 getCorporaWithParentId :: Int -> Cmd err [Node HyperdataCorpus]
316 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
318 ------------------------------------------------------------------------
319 selectNodesWithParentID :: Int -> Query NodeRead
320 selectNodesWithParentID n = proc () -> do
321 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
322 restrict -< parent_id .== (pgInt4 n)
325 selectNodesWithType :: Column PGInt4 -> Query NodeRead
326 selectNodesWithType type_id = proc () -> do
327 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
328 restrict -< tn .== type_id
331 type JSONB = QueryRunnerColumnDefault PGJsonb
333 getNode :: JSONB a => Int -> proxy a -> Cmd err (Node a)
335 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runOpaQuery (limit 1 $ selectNode (pgInt4 id))
337 getNodesWithType :: Column PGInt4 -> Cmd err [Node HyperdataDocument]
338 getNodesWithType = runOpaQuery . selectNodesWithType
340 ------------------------------------------------------------------------
341 ------------------------------------------------------------------------
342 defaultUser :: HyperdataUser
343 defaultUser = HyperdataUser (Just $ (pack . show) EN)
345 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite
346 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
348 name = maybe "User" identity maybeName
349 user = maybe defaultUser identity maybeHyperdata
350 ------------------------------------------------------------------------
351 defaultFolder :: HyperdataFolder
352 defaultFolder = HyperdataFolder (Just "Markdown Description")
354 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite
355 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
357 name = maybe "Folder" identity maybeName
358 folder = maybe defaultFolder identity maybeFolder
359 ------------------------------------------------------------------------
360 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite
361 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
363 name = maybe "Corpus" identity maybeName
364 corpus = maybe defaultCorpus identity maybeCorpus
365 --------------------------
366 defaultDocument :: HyperdataDocument
367 defaultDocument = hyperdataDocument
369 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite
370 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
372 name = maybe "Document" identity maybeName
373 doc = maybe defaultDocument identity maybeDocument
374 ------------------------------------------------------------------------
375 defaultAnnuaire :: HyperdataAnnuaire
376 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
378 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite
379 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
381 name = maybe "Annuaire" identity maybeName
382 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
383 --------------------------
385 ------------------------------------------------------------------------
386 arbitraryList :: HyperdataList
387 arbitraryList = HyperdataList (Just "Preferences")
389 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite
390 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
392 name = maybe "Listes" identity maybeName
393 list = maybe arbitraryList identity maybeList
395 ------------------------------------------------------------------------
396 arbitraryGraph :: HyperdataGraph
397 arbitraryGraph = HyperdataGraph (Just "Preferences")
399 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite
400 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
402 name = maybe "Graph" identity maybeName
403 graph = maybe arbitraryGraph identity maybeGraph
405 ------------------------------------------------------------------------
407 arbitraryDashboard :: HyperdataDashboard
408 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
410 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite
411 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
413 name = maybe "Dashboard" identity maybeName
414 dashboard = maybe arbitraryDashboard identity maybeDashboard
416 ------------------------------------------------------------------------
417 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite
418 node nodeType name hyperData parentId userId = Node Nothing (pgInt4 typeId) (pgInt4 userId) (pgInt4 <$> parentId) (pgStrictText name) Nothing (pgJSONB $ cs $ encode hyperData)
420 typeId = nodeTypeId nodeType
422 -------------------------------
423 insertNodes :: [NodeWrite] -> Cmd err Int64
424 insertNodes ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
426 insertNodesR :: [NodeWrite] -> Cmd err [Int]
427 insertNodesR ns = mkCmd $ \conn ->
428 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _) -> i)) Nothing)
430 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
431 insertNodesWithParent pid ns = insertNodes (set node_parentId (pgInt4 <$> pid) <$> ns)
433 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [Int]
434 insertNodesWithParentR pid ns = insertNodesR (set node_parentId (pgInt4 <$> pid) <$> ns)
435 ------------------------------------------------------------------------
436 -- TODO Hierachy of Nodes
437 -- post and get same types Node' and update if changes
439 {- TODO semantic to achieve
440 post c uid pid [ Node' NodeCorpus "name" "{}" []
441 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
442 , Node' NodeDocument "title" "jsonData" []
447 ------------------------------------------------------------------------
450 -- currently this function remove the child relation
451 -- needs a Temporary type between Node' and NodeWriteT
452 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWrite
453 node2table uid pid (Node' nt txt v []) = Node Nothing (pgInt4$ nodeTypeId nt) (pgInt4 uid) (fmap pgInt4 pid) (pgStrictText txt) Nothing (pgStrictJSONB $ cs $ encode v)
454 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
457 data Node' = Node' { _n_type :: NodeType
460 , _n_children :: [Node']
463 mkNode :: [NodeWrite] -> Cmd err Int64
464 mkNode ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
466 mkNodeR :: [NodeWrite] -> Cmd err [Int]
467 mkNodeR ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable ns (_node_id)
469 ------------------------------------------------------------------------
471 data NewNode = NewNode { _newNodeId :: Int
472 , _newNodeChildren :: [Int] }
475 postNode :: HasNodeError err => UserId -> Maybe ParentId -> Node' -> Cmd err NewNode
476 postNode uid pid (Node' nt txt v []) = do
477 pids <- mkNodeR [node2table uid pid (Node' nt txt v [])]
479 [pid] -> pure $ NewNode pid []
480 _ -> nodeError ManyParents
482 postNode uid pid (Node' NodeCorpus txt v ns) = do
483 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
484 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
485 pure $ NewNode pid' pids
487 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
488 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
489 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
490 pure $ NewNode pid' pids
491 postNode _ _ (Node' _ _ _ _) = nodeError NotImplYet
494 childWith :: UserId -> ParentId -> Node' -> NodeWrite
495 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
496 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
497 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
500 mk :: NodeType -> Maybe ParentId -> Text -> Cmd err [Int]
501 mk nt pId name = mk' nt userId pId name
505 mk' :: NodeType -> UserId -> Maybe ParentId -> Text -> Cmd err [Int]
506 mk' nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId]
508 hd = HyperdataUser . Just . pack $ show EN
512 mk'' :: HasNodeError err => NodeType -> Maybe ParentId -> UserId -> Name -> Cmd err [Int]
513 mk'' NodeUser Nothing uId name = mk' NodeUser uId Nothing name
514 mk'' NodeUser _ _ _ = nodeError UserNoParent
515 mk'' _ Nothing _ _ = nodeError HasParent
516 mk'' nt pId uId name = mk' nt uId pId name
518 mkRoot :: HasNodeError err => Username -> UserId -> Cmd err [Int]
519 mkRoot uname uId = case uId > 0 of
520 False -> nodeError NegativeId
521 True -> mk'' NodeUser Nothing uId uname
523 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd err [Int]
524 mkCorpus n h p u = insertNodesR [nodeCorpusW n h p u]
526 getOrMkList :: HasNodeError err => ParentId -> UserId -> Cmd err Int
527 getOrMkList pId uId =
528 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
530 mkList' pId uId = maybe (nodeError MkNode) pure . headMay =<< mkList pId uId
532 -- | TODO remove defaultList
533 defaultList :: HasNodeError err => CorpusId -> Cmd err ListId
535 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
537 mkList :: HasNodeError err => ParentId -> UserId -> Cmd err [Int]
538 mkList p u = insertNodesR [nodeListW Nothing Nothing p u]
540 mkGraph :: ParentId -> UserId -> Cmd err [Int]
541 mkGraph p u = insertNodesR [nodeGraphW Nothing Nothing p u]
543 mkDashboard :: ParentId -> UserId -> Cmd err [Int]
544 mkDashboard p u = insertNodesR [nodeDashboardW Nothing Nothing p u]
546 mkAnnuaire :: ParentId -> UserId -> Cmd err [Int]
547 mkAnnuaire p u = insertNodesR [nodeAnnuaireW Nothing Nothing p u]
549 -- | Default CorpusId Master and ListId Master