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
13 {-# OPTIONS_GHC -fno-warn-orphans #-}
15 {-# LANGUAGE Arrows #-}
16 {-# LANGUAGE ConstraintKinds #-}
17 {-# LANGUAGE FunctionalDependencies #-}
18 {-# LANGUAGE QuasiQuotes #-}
19 {-# LANGUAGE TemplateHaskell #-}
20 {-# LANGUAGE TypeFamilies #-}
22 module Gargantext.Database.Query.Table.Node
25 import Control.Arrow (returnA)
26 import Control.Lens (set, view)
28 import Data.Maybe (fromMaybe)
29 import Data.Text (Text)
30 import qualified Database.PostgreSQL.Simple as DPS
31 import Database.PostgreSQL.Simple.SqlQQ (sql)
32 import Opaleye hiding (FromField)
33 import Prelude hiding (null, id, map, sum)
35 import Gargantext.Core
36 import Gargantext.Core.Types
37 import Gargantext.Database.Admin.Types.Hyperdata
38 import Gargantext.Database.Admin.Types.Hyperdata.Default
39 import Gargantext.Database.Prelude
40 import Gargantext.Database.Query.Filter (limit', offset')
41 import Gargantext.Database.Query.Table.Node.Error
42 import Gargantext.Database.Schema.Node
43 import Gargantext.Prelude hiding (sum, head)
46 queryNodeSearchTable :: Select NodeSearchRead
47 queryNodeSearchTable = selectTable nodeTableSearch
49 selectNode :: Column SqlInt4 -> Select NodeRead
50 selectNode id' = proc () -> do
51 row <- queryNodeTable -< ()
52 restrict -< _node_id row .== id'
55 runGetNodes :: Select NodeRead -> Cmd err [Node HyperdataAny]
56 runGetNodes = runOpaQuery
58 ------------------------------------------------------------------------
59 ------------------------------------------------------------------------
60 -- | order by publication date
61 -- Favorites (Bool), node_ngrams
62 selectNodesWith :: HasDBid NodeType
63 => ParentId -> Maybe NodeType
64 -> Maybe Offset -> Maybe Limit -> Select NodeRead
65 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
66 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
67 limit' maybeLimit $ offset' maybeOffset
68 $ orderBy (asc _node_id)
69 $ selectNodesWith' parentId maybeNodeType
71 selectNodesWith' :: HasDBid NodeType
72 => ParentId -> Maybe NodeType -> Select NodeRead
73 selectNodesWith' parentId maybeNodeType = proc () -> do
74 node' <- (proc () -> do
75 row@(Node _ _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
76 restrict -< parentId' .== (pgNodeId parentId)
78 let typeId' = maybe 0 toDBid maybeNodeType
80 restrict -< if typeId' > 0
81 then typeId .== (sqlInt4 (typeId' :: Int))
83 returnA -< row ) -< ()
86 deleteNode :: NodeId -> Cmd err Int
87 deleteNode n = mkCmd $ \conn ->
88 fromIntegral <$> runDelete_ conn
90 (\(Node n_id _ _ _ _ _ _ _) -> n_id .== pgNodeId n)
94 deleteNodes :: [NodeId] -> Cmd err Int
95 deleteNodes ns = mkCmd $ \conn ->
96 fromIntegral <$> runDelete_ conn
98 (\(Node n_id _ _ _ _ _ _ _) -> in_ ((map pgNodeId ns)) n_id)
102 -- TODO: NodeType should match with `a'
103 getNodesWith :: (JSONB a, HasDBid NodeType) => NodeId -> proxy a -> Maybe NodeType
104 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
105 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
106 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
108 -- TODO: Why is the second parameter ignored?
109 -- TODO: Why not use getNodesWith?
110 getNodesWithParentId :: (Hyperdata a, JSONB a)
113 getNodesWithParentId n = runOpaQuery $ selectNodesWithParentID n'
120 -- | Given a node id, find it's closest parent of given type
121 -- NOTE: This isn't too optimal: can make successive queries depending on how
122 -- deeply nested the child is.
123 getClosestParentIdByType :: HasDBid NodeType
126 -> Cmd err (Maybe NodeId)
127 getClosestParentIdByType nId nType = do
128 result <- runPGSQuery query (nId, 0 :: Int)
130 [(NodeId parentId, pTypename)] -> do
131 if toDBid nType == pTypename then
132 pure $ Just $ NodeId parentId
134 getClosestParentIdByType (NodeId parentId) nType
139 SELECT n2.id, n2.typename
141 JOIN nodes n2 ON n1.parent_id = n2.id
142 WHERE n1.id = ? AND 0 = ?;
145 -- | Similar to `getClosestParentIdByType` but includes current node
147 getClosestParentIdByType' :: HasDBid NodeType
150 -> Cmd err (Maybe NodeId)
151 getClosestParentIdByType' nId nType = do
152 result <- runPGSQuery query (nId, 0 :: Int)
154 [(NodeId id, pTypename)] -> do
155 if toDBid nType == pTypename then
156 pure $ Just $ NodeId id
158 getClosestParentIdByType nId nType
163 SELECT n.id, n.typename
165 WHERE n.id = ? AND 0 = ?;
168 -- | Given a node id, find all it's children (no matter how deep) of
170 getChildrenByType :: HasDBid NodeType
174 getChildrenByType nId nType = do
175 result <- runPGSQuery query (nId, 0 :: Int)
176 children_lst <- mapM (\(id, _) -> getChildrenByType id nType) result
177 pure $ concat $ [fst <$> filter (\(_, pTypename) -> pTypename == toDBid nType) result] ++ children_lst
181 SELECT n.id, n.typename
183 WHERE n.parent_id = ? AND 0 = ?;
186 ------------------------------------------------------------------------
187 getDocumentsV3WithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocumentV3]
188 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
190 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
191 getDocumentsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocument]
192 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
194 getListsModelWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataModel]
195 getListsModelWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeModel)
197 getCorporaWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataCorpus]
198 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
200 ------------------------------------------------------------------------
201 selectNodesWithParentID :: NodeId -> Select NodeRead
202 selectNodesWithParentID n = proc () -> do
203 row@(Node _ _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
204 restrict -< parent_id .== (pgNodeId n)
208 ------------------------------------------------------------------------
210 -- runCmdReplEasy (getNodesWithType NodeList (Proxy :: Proxy HyperdataList))
211 getNodesWithType :: (HasNodeError err, JSONB a, HasDBid NodeType) => NodeType -> proxy a -> Cmd err [Node a]
212 getNodesWithType nt _ = runOpaQuery $ selectNodesWithType nt
214 selectNodesWithType :: HasDBid NodeType
215 => NodeType -> Select NodeRead
216 selectNodesWithType nt' = proc () -> do
217 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
218 restrict -< tn .== (sqlInt4 $ toDBid nt')
221 getNodesIdWithType :: (HasNodeError err, HasDBid NodeType) => NodeType -> Cmd err [NodeId]
222 getNodesIdWithType nt = do
223 ns <- runOpaQuery $ selectNodesIdWithType nt
226 selectNodesIdWithType :: HasDBid NodeType
227 => NodeType -> Select (Column SqlInt4)
228 selectNodesIdWithType nt = proc () -> do
229 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
230 restrict -< tn .== (sqlInt4 $ toDBid nt)
231 returnA -< _node_id row
233 ------------------------------------------------------------------------
236 getNode :: HasNodeError err => NodeId -> Cmd err (Node Value)
238 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
240 Nothing -> nodeError (DoesNotExist nId)
243 getNodeWith :: (HasNodeError err, JSONB a)
244 => NodeId -> proxy a -> Cmd err (Node a)
245 getNodeWith nId _ = do
246 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
248 Nothing -> nodeError (DoesNotExist nId)
252 ------------------------------------------------------------------------
253 -- | Sugar to insert Node with NodeType in Database
254 insertDefaultNode :: HasDBid NodeType
255 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
256 insertDefaultNode nt p u = insertNode nt Nothing Nothing p u
258 insertDefaultNodeIfNotExists :: HasDBid NodeType
259 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
260 insertDefaultNodeIfNotExists nt p u = do
261 children <- getChildrenByType p nt
263 [] -> insertDefaultNode nt p u
266 insertNode :: HasDBid NodeType
267 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> Cmd err [NodeId]
268 insertNode nt n h p u = insertNodesR [nodeW nt n h p u]
270 nodeW :: HasDBid NodeType
271 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> NodeWrite
272 nodeW nt n h p u = node nt n' h' (Just p) u
274 n' = fromMaybe (defaultName nt) n
275 h' = maybe (defaultHyperdata nt) identity h
277 ------------------------------------------------------------------------
278 node :: (ToJSON a, Hyperdata a, HasDBid NodeType)
285 node nodeType name hyperData parentId userId =
289 (pgNodeId <$> parentId)
292 (sqlJSONB $ cs $ encode hyperData)
294 typeId = toDBid nodeType
296 -------------------------------
297 insertNodes :: [NodeWrite] -> Cmd err Int64
298 insertNodes ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns rCount Nothing
301 insertNodes' :: [Node a] -> Cmd err Int64
302 insertNodes' ns = mkCmd $ \conn -> runInsert_ conn
303 $ Insert nodeTable ns' rCount Nothing
306 ns' = map (\(Node i t u p n d h)
307 -> Node (pgNodeId <$> i)
313 (pgJSONB $ cs $ encode h)
317 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
318 insertNodesR ns = mkCmd $ \conn ->
319 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _ _) -> i)) Nothing)
321 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
322 insertNodesWithParent pid ns = insertNodes (set node_parent_id (pgNodeId <$> pid) <$> ns)
324 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
325 insertNodesWithParentR pid ns = insertNodesR (set node_parent_id (pgNodeId <$> pid) <$> ns)
326 ------------------------------------------------------------------------
328 -- currently this function removes the child relation
329 -- needs a Temporary type between Node' and NodeWriteT
331 node2table :: HasDBid NodeType
332 => UserId -> Maybe ParentId -> Node' -> NodeWrite
333 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)
334 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
337 data Node' = Node' { _n_type :: NodeType
340 , _n_children :: [Node']
343 mkNodes :: [NodeWrite] -> Cmd err Int64
344 mkNodes ns = mkCmd $ \conn -> runInsert_ conn
345 $ Insert nodeTable ns rCount Nothing
347 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
348 mkNodeR ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns (rReturning _node_id) Nothing
350 ------------------------------------------------------------------------
351 childWith :: HasDBid NodeType
352 => UserId -> ParentId -> Node' -> NodeWrite
353 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
354 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
355 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
358 -- =================================================================== --
360 -- CorpusDocument is a corpus made from a set of documents
361 -- CorpusContact is a corpus made from a set of contacts (syn of Annuaire)
362 data CorpusType = CorpusDocument | CorpusContact
366 mk :: HasDBid NodeType => Maybe Name -> Maybe a -> ParentId -> UserId -> Cmd err [NodeId]
368 instance MkCorpus HyperdataCorpus
370 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
371 mk n (Just h) p u = insertNode NodeCorpus n (Just $ DefaultCorpus h) p u
374 instance MkCorpus HyperdataAnnuaire
376 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
377 mk n (Just h) p u = insertNode NodeAnnuaire n (Just $ DefaultAnnuaire h) p u
380 getOrMkList :: (HasNodeError err, HasDBid NodeType)
384 getOrMkList pId uId =
385 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
387 mkList' pId' uId' = maybe (nodeError MkNode) pure . headMay =<< insertDefaultNode NodeList pId' uId'
389 -- | TODO remove defaultList
390 defaultList :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err ListId
392 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
394 defaultListMaybe :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err (Maybe NodeId)
395 defaultListMaybe cId = headMay <$> map (view node_id ) <$> getListsWithParentId cId
397 getListsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataList]
398 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)