2 import Gargantext.Database.Prelude (Cmd, runPGSQuery)
3 Module : Gargantext.Database.Query.Table.Node
4 Description : Main Tools of Node to the database
5 Copyright : (c) CNRS, 2017-Present
6 License : AGPL + CECILL v3
7 Maintainer : team@gargantext.org
8 Stability : experimental
12 {-# OPTIONS_GHC -fno-warn-orphans #-}
14 {-# LANGUAGE Arrows #-}
15 {-# LANGUAGE ConstraintKinds #-}
16 {-# LANGUAGE FunctionalDependencies #-}
17 {-# LANGUAGE QuasiQuotes #-}
18 {-# LANGUAGE TemplateHaskell #-}
19 {-# LANGUAGE TypeFamilies #-}
21 module Gargantext.Database.Query.Table.Node
24 import Control.Arrow (returnA)
25 import Control.Lens (set, view)
27 import Data.Maybe (fromMaybe)
28 import Data.Text (Text)
29 import Database.PostgreSQL.Simple.SqlQQ (sql)
30 import Opaleye hiding (FromField)
31 import Prelude hiding (null, id, map, sum)
33 import Gargantext.Core
34 import Gargantext.Core.Types
35 import Gargantext.Core.Types.Query (Limit, Offset)
36 import Gargantext.Database.Admin.Types.Hyperdata
37 import Gargantext.Database.Admin.Types.Hyperdata.Default
38 import Gargantext.Database.Prelude
39 import Gargantext.Database.Query.Filter (limit', offset')
40 import Gargantext.Database.Query.Table.Node.Error
41 import Gargantext.Database.Schema.Node
42 import Gargantext.Prelude hiding (sum, head)
44 import qualified Database.PostgreSQL.Simple as PGS
47 queryNodeSearchTable :: Select NodeSearchRead
48 queryNodeSearchTable = selectTable nodeTableSearch
50 selectNode :: Column SqlInt4 -> Select NodeRead
51 selectNode id' = proc () -> do
52 row <- queryNodeTable -< ()
53 restrict -< _node_id row .== id'
56 runGetNodes :: Select NodeRead -> Cmd err [Node HyperdataAny]
57 runGetNodes = runOpaQuery
59 ------------------------------------------------------------------------
60 ------------------------------------------------------------------------
61 -- | order by publication date
62 -- Favorites (Bool), node_ngrams
63 selectNodesWith :: HasDBid NodeType
64 => ParentId -> Maybe NodeType
65 -> Maybe Offset -> Maybe Limit -> Select NodeRead
66 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
67 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
68 limit' maybeLimit $ offset' maybeOffset
69 $ orderBy (asc _node_id)
70 $ selectNodesWith' parentId maybeNodeType
72 selectNodesWith' :: HasDBid NodeType
73 => ParentId -> Maybe NodeType -> Select NodeRead
74 selectNodesWith' parentId maybeNodeType = proc () -> do
75 node' <- (proc () -> do
76 row@(Node _ _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
77 restrict -< parentId' .== (pgNodeId parentId)
79 let typeId' = maybe 0 toDBid maybeNodeType
81 restrict -< if typeId' > 0
82 then typeId .== (sqlInt4 (typeId' :: Int))
84 returnA -< row ) -< ()
87 deleteNode :: NodeId -> Cmd err Int
88 deleteNode n = mkCmd $ \conn ->
89 fromIntegral <$> runDelete_ conn
91 (\(Node n_id _ _ _ _ _ _ _) -> n_id .== pgNodeId n)
95 deleteNodes :: [NodeId] -> Cmd err Int
96 deleteNodes ns = mkCmd $ \conn ->
97 fromIntegral <$> runDelete_ conn
99 (\(Node n_id _ _ _ _ _ _ _) -> in_ ((map pgNodeId ns)) n_id)
103 -- TODO: NodeType should match with `a'
104 getNodesWith :: (JSONB a, HasDBid NodeType) => NodeId -> proxy a -> Maybe NodeType
105 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
106 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
107 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
109 -- TODO: Why is the second parameter ignored?
110 -- TODO: Why not use getNodesWith?
111 getNodesWithParentId :: (Hyperdata a, JSONB a)
114 getNodesWithParentId n = runOpaQuery $ selectNodesWithParentID n'
121 -- | Given a node id, find it's closest parent of given type
122 -- NOTE: This isn't too optimal: can make successive queries depending on how
123 -- deeply nested the child is.
124 getClosestParentIdByType :: HasDBid NodeType
127 -> Cmd err (Maybe NodeId)
128 getClosestParentIdByType nId nType = do
129 result <- runPGSQuery query (PGS.Only nId)
131 [(NodeId parentId, pTypename)] -> do
132 if toDBid nType == pTypename then
133 pure $ Just $ NodeId parentId
135 getClosestParentIdByType (NodeId parentId) nType
140 SELECT n2.id, n2.typename
142 JOIN nodes n2 ON n1.parent_id = n2.id
146 -- | Similar to `getClosestParentIdByType` but includes current node
148 getClosestParentIdByType' :: HasDBid NodeType
151 -> Cmd err (Maybe NodeId)
152 getClosestParentIdByType' nId nType = do
153 result <- runPGSQuery query (PGS.Only nId)
155 [(NodeId id, pTypename)] -> do
156 if toDBid nType == pTypename then
157 pure $ Just $ NodeId id
159 getClosestParentIdByType nId nType
164 SELECT n.id, n.typename
169 -- | Given a node id, find all it's children (no matter how deep) of
171 getChildrenByType :: HasDBid NodeType
175 getChildrenByType nId nType = do
176 result <- runPGSQuery query (PGS.Only nId)
177 children_lst <- mapM (\(id, _) -> getChildrenByType id nType) result
178 pure $ concat $ [fst <$> filter (\(_, pTypename) -> pTypename == toDBid nType) result] ++ children_lst
182 SELECT n.id, n.typename
184 WHERE n.parent_id = ?;
187 ------------------------------------------------------------------------
188 getDocumentsV3WithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocumentV3]
189 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
191 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
192 getDocumentsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocument]
193 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
195 getListsModelWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataModel]
196 getListsModelWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeModel)
198 getCorporaWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataCorpus]
199 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
201 ------------------------------------------------------------------------
202 selectNodesWithParentID :: NodeId -> Select NodeRead
203 selectNodesWithParentID n = proc () -> do
204 row@(Node _ _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
205 restrict -< parent_id .== pgNodeId n
209 ------------------------------------------------------------------------
211 -- runCmdReplEasy (getNodesWithType NodeList (Proxy :: Proxy HyperdataList))
212 getNodesWithType :: (HasNodeError err, JSONB a, HasDBid NodeType) => NodeType -> proxy a -> Cmd err [Node a]
213 getNodesWithType nt _ = runOpaQuery $ selectNodesWithType nt
215 selectNodesWithType :: HasDBid NodeType
216 => NodeType -> Select NodeRead
217 selectNodesWithType nt' = proc () -> do
218 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
219 restrict -< tn .== sqlInt4 (toDBid nt')
222 getNodeWithType :: (HasNodeError err, JSONB a, HasDBid NodeType)
227 getNodeWithType nId nt _ = runOpaQuery $ selectNodeWithType nId nt
229 selectNodeWithType :: HasDBid NodeType
230 => NodeId -> NodeType -> Select NodeRead
231 selectNodeWithType (NodeId nId') nt' = proc () -> do
232 row@(Node ti _ tn _ _ _ _ _) <- queryNodeTable -< ()
233 restrict -< ti .== sqlInt4 nId'
234 restrict -< tn .== sqlInt4 (toDBid nt')
237 getNodesIdWithType :: (HasNodeError err, HasDBid NodeType) => NodeType -> Cmd err [NodeId]
238 getNodesIdWithType nt = do
239 ns <- runOpaQuery $ selectNodesIdWithType nt
242 selectNodesIdWithType :: HasDBid NodeType
243 => NodeType -> Select (Column SqlInt4)
244 selectNodesIdWithType nt = proc () -> do
245 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
246 restrict -< tn .== (sqlInt4 $ toDBid nt)
247 returnA -< _node_id row
249 ------------------------------------------------------------------------
251 nodeExists :: (HasNodeError err) => NodeId -> Cmd err Bool
252 nodeExists nId = (== [PGS.Only True])
253 <$> runPGSQuery [sql|SELECT true FROM nodes WHERE id = ? |] (PGS.Only nId)
255 getNode :: HasNodeError err => NodeId -> Cmd err (Node Value)
257 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
259 Nothing -> nodeError (DoesNotExist nId)
262 getNodeWith :: (HasNodeError err, JSONB a)
263 => NodeId -> proxy a -> Cmd err (Node a)
264 getNodeWith nId _ = do
265 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
267 Nothing -> nodeError (DoesNotExist nId)
271 ------------------------------------------------------------------------
272 -- | Sugar to insert Node with NodeType in Database
273 insertDefaultNode :: HasDBid NodeType
274 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
275 insertDefaultNode nt p u = insertNode nt Nothing Nothing p u
277 insertDefaultNodeIfNotExists :: HasDBid NodeType
278 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
279 insertDefaultNodeIfNotExists nt p u = do
280 children <- getChildrenByType p nt
282 [] -> insertDefaultNode nt p u
285 insertNode :: HasDBid NodeType
286 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> Cmd err [NodeId]
287 insertNode nt n h p u = insertNodesR [nodeW nt n h p u]
289 nodeW :: HasDBid NodeType
290 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> NodeWrite
291 nodeW nt n h p u = node nt n' h' (Just p) u
293 n' = fromMaybe (defaultName nt) n
294 h' = maybe (defaultHyperdata nt) identity h
296 ------------------------------------------------------------------------
297 node :: (ToJSON a, Hyperdata a, HasDBid NodeType)
304 node nodeType name hyperData parentId userId =
308 (pgNodeId <$> parentId)
311 (sqlJSONB $ cs $ encode hyperData)
313 typeId = toDBid nodeType
315 -------------------------------
316 insertNodes :: [NodeWrite] -> Cmd err Int64
317 insertNodes ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns rCount Nothing
320 insertNodes' :: [Node a] -> Cmd err Int64
321 insertNodes' ns = mkCmd $ \conn -> runInsert_ conn
322 $ Insert nodeTable ns' rCount Nothing
325 ns' = map (\(Node i t u p n d h)
326 -> Node (pgNodeId <$> i)
332 (pgJSONB $ cs $ encode h)
336 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
337 insertNodesR ns = mkCmd $ \conn ->
338 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _ _) -> i)) Nothing)
340 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
341 insertNodesWithParent pid ns = insertNodes (set node_parent_id (pgNodeId <$> pid) <$> ns)
343 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
344 insertNodesWithParentR pid ns = insertNodesR (set node_parent_id (pgNodeId <$> pid) <$> ns)
345 ------------------------------------------------------------------------
347 -- currently this function removes the child relation
348 -- needs a Temporary type between Node' and NodeWriteT
350 node2table :: HasDBid NodeType
351 => UserId -> Maybe ParentId -> Node' -> NodeWrite
352 node2table uid pid (Node' nt txt v []) = Node Nothing Nothing (sqlInt4 $ toDBid nt) (sqlInt4 uid) (fmap pgNodeId pid) (sqlStrictText txt) Nothing (sqlStrictJSONB $ cs $ encode v)
353 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
356 data Node' = Node' { _n_type :: NodeType
359 , _n_children :: [Node']
362 mkNodes :: [NodeWrite] -> Cmd err Int64
363 mkNodes ns = mkCmd $ \conn -> runInsert_ conn
364 $ Insert nodeTable ns rCount Nothing
366 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
367 mkNodeR ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns (rReturning _node_id) Nothing
369 ------------------------------------------------------------------------
370 childWith :: HasDBid NodeType
371 => UserId -> ParentId -> Node' -> NodeWrite
372 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
373 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
374 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
377 -- =================================================================== --
379 -- CorpusDocument is a corpus made from a set of documents
380 -- CorpusContact is a corpus made from a set of contacts (syn of Annuaire)
381 data CorpusType = CorpusDocument | CorpusContact
385 mk :: HasDBid NodeType => Maybe Name -> Maybe a -> ParentId -> UserId -> Cmd err [NodeId]
387 instance MkCorpus HyperdataCorpus
389 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
390 mk n (Just h) p u = insertNode NodeCorpus n (Just $ DefaultCorpus h) p u
393 instance MkCorpus HyperdataAnnuaire
395 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
396 mk n (Just h) p u = insertNode NodeAnnuaire n (Just $ DefaultAnnuaire h) p u
399 getOrMkList :: (HasNodeError err, HasDBid NodeType)
403 getOrMkList pId uId =
404 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
406 mkList' pId' uId' = maybe (nodeError MkNode) pure . headMay =<< insertDefaultNode NodeList pId' uId'
408 -- | TODO remove defaultList
409 defaultList :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err ListId
411 maybe (nodeError (NoListFound cId)) (pure . view node_id) . headMay =<< getListsWithParentId cId
413 defaultListMaybe :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err (Maybe NodeId)
414 defaultListMaybe cId = headMay <$> map (view node_id ) <$> getListsWithParentId cId
416 getListsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataList]
417 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)