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 qualified Database.PostgreSQL.Simple as DPS
30 import Database.PostgreSQL.Simple.SqlQQ (sql)
31 import Opaleye hiding (FromField)
32 import Prelude hiding (null, id, map, sum)
34 import Gargantext.Core
35 import Gargantext.Core.Types
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 queryNodeSearchTable :: Select NodeSearchRead
45 queryNodeSearchTable = selectTable nodeTableSearch
47 selectNode :: Column SqlInt4 -> Select NodeRead
48 selectNode id' = proc () -> do
49 row <- queryNodeTable -< ()
50 restrict -< _node_id row .== id'
53 runGetNodes :: Select NodeRead -> Cmd err [Node HyperdataAny]
54 runGetNodes = runOpaQuery
56 ------------------------------------------------------------------------
57 ------------------------------------------------------------------------
58 -- | order by publication date
59 -- Favorites (Bool), node_ngrams
60 selectNodesWith :: HasDBid NodeType
61 => ParentId -> Maybe NodeType
62 -> Maybe Offset -> Maybe Limit -> Select NodeRead
63 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
64 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
65 limit' maybeLimit $ offset' maybeOffset
66 $ orderBy (asc _node_id)
67 $ selectNodesWith' parentId maybeNodeType
69 selectNodesWith' :: HasDBid NodeType
70 => ParentId -> Maybe NodeType -> Select NodeRead
71 selectNodesWith' parentId maybeNodeType = proc () -> do
72 node' <- (proc () -> do
73 row@(Node _ _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
74 restrict -< parentId' .== (pgNodeId parentId)
76 let typeId' = maybe 0 toDBid maybeNodeType
78 restrict -< if typeId' > 0
79 then typeId .== (sqlInt4 (typeId' :: Int))
81 returnA -< row ) -< ()
84 deleteNode :: NodeId -> Cmd err Int
85 deleteNode n = mkCmd $ \conn ->
86 fromIntegral <$> runDelete_ conn
88 (\(Node n_id _ _ _ _ _ _ _) -> n_id .== pgNodeId n)
92 deleteNodes :: [NodeId] -> Cmd err Int
93 deleteNodes ns = mkCmd $ \conn ->
94 fromIntegral <$> runDelete_ conn
96 (\(Node n_id _ _ _ _ _ _ _) -> in_ ((map pgNodeId ns)) n_id)
100 -- TODO: NodeType should match with `a'
101 getNodesWith :: (JSONB a, HasDBid NodeType) => NodeId -> proxy a -> Maybe NodeType
102 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
103 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
104 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
106 -- TODO: Why is the second parameter ignored?
107 -- TODO: Why not use getNodesWith?
108 getNodesWithParentId :: (Hyperdata a, JSONB a)
111 getNodesWithParentId n = runOpaQuery $ selectNodesWithParentID n'
118 -- | Given a node id, find it's closest parent of given type
119 -- NOTE: This isn't too optimal: can make successive queries depending on how
120 -- deeply nested the child is.
121 getClosestParentIdByType :: HasDBid NodeType
124 -> Cmd err (Maybe NodeId)
125 getClosestParentIdByType nId nType = do
126 result <- runPGSQuery query (nId, 0 :: Int)
128 [(NodeId parentId, pTypename)] -> do
129 if toDBid nType == pTypename then
130 pure $ Just $ NodeId parentId
132 getClosestParentIdByType (NodeId parentId) nType
137 SELECT n2.id, n2.typename
139 JOIN nodes n2 ON n1.parent_id = n2.id
140 WHERE n1.id = ? AND 0 = ?;
143 -- | Similar to `getClosestParentIdByType` but includes current node
145 getClosestParentIdByType' :: HasDBid NodeType
148 -> Cmd err (Maybe NodeId)
149 getClosestParentIdByType' nId nType = do
150 result <- runPGSQuery query (nId, 0 :: Int)
152 [(NodeId id, pTypename)] -> do
153 if toDBid nType == pTypename then
154 pure $ Just $ NodeId id
156 getClosestParentIdByType nId nType
161 SELECT n.id, n.typename
163 WHERE n.id = ? AND 0 = ?;
166 -- | Given a node id, find all it's children (no matter how deep) of
168 getChildrenByType :: HasDBid NodeType
172 getChildrenByType nId nType = do
173 result <- runPGSQuery query (nId, 0 :: Int)
174 children_lst <- mapM (\(id, _) -> getChildrenByType id nType) result
175 pure $ concat $ [fst <$> filter (\(_, pTypename) -> pTypename == toDBid nType) result] ++ children_lst
179 SELECT n.id, n.typename
181 WHERE n.parent_id = ? AND 0 = ?;
184 ------------------------------------------------------------------------
185 getDocumentsV3WithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocumentV3]
186 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
188 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
189 getDocumentsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataDocument]
190 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
192 getListsModelWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataModel]
193 getListsModelWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeModel)
195 getCorporaWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataCorpus]
196 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
198 ------------------------------------------------------------------------
199 selectNodesWithParentID :: NodeId -> Select NodeRead
200 selectNodesWithParentID n = proc () -> do
201 row@(Node _ _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
202 restrict -< parent_id .== (pgNodeId n)
206 ------------------------------------------------------------------------
208 -- runCmdReplEasy (getNodesWithType NodeList (Proxy :: Proxy HyperdataList))
209 getNodesWithType :: (HasNodeError err, JSONB a, HasDBid NodeType) => NodeType -> proxy a -> Cmd err [Node a]
210 getNodesWithType nt _ = runOpaQuery $ selectNodesWithType nt
212 selectNodesWithType :: HasDBid NodeType
213 => NodeType -> Select NodeRead
214 selectNodesWithType nt' = proc () -> do
215 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
216 restrict -< tn .== (sqlInt4 $ toDBid nt')
219 getNodesIdWithType :: (HasNodeError err, HasDBid NodeType) => NodeType -> Cmd err [NodeId]
220 getNodesIdWithType nt = do
221 ns <- runOpaQuery $ selectNodesIdWithType nt
224 selectNodesIdWithType :: HasDBid NodeType
225 => NodeType -> Select (Column SqlInt4)
226 selectNodesIdWithType nt = proc () -> do
227 row@(Node _ _ tn _ _ _ _ _) <- queryNodeTable -< ()
228 restrict -< tn .== (sqlInt4 $ toDBid nt)
229 returnA -< _node_id row
231 ------------------------------------------------------------------------
233 nodeExists :: (HasNodeError err) => NodeId -> Cmd err Bool
234 nodeExists nId = (== [DPS.Only True])
235 <$> runPGSQuery [sql|SELECT true FROM nodes WHERE id = ? AND ?|] (nId, True)
237 getNode :: HasNodeError err => NodeId -> Cmd err (Node Value)
239 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
241 Nothing -> nodeError (DoesNotExist nId)
244 getNodeWith :: (HasNodeError err, JSONB a)
245 => NodeId -> proxy a -> Cmd err (Node a)
246 getNodeWith nId _ = do
247 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
249 Nothing -> nodeError (DoesNotExist nId)
253 ------------------------------------------------------------------------
254 -- | Sugar to insert Node with NodeType in Database
255 insertDefaultNode :: HasDBid NodeType
256 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
257 insertDefaultNode nt p u = insertNode nt Nothing Nothing p u
259 insertDefaultNodeIfNotExists :: HasDBid NodeType
260 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
261 insertDefaultNodeIfNotExists nt p u = do
262 children <- getChildrenByType p nt
264 [] -> insertDefaultNode nt p u
267 insertNode :: HasDBid NodeType
268 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> Cmd err [NodeId]
269 insertNode nt n h p u = insertNodesR [nodeW nt n h p u]
271 nodeW :: HasDBid NodeType
272 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> NodeWrite
273 nodeW nt n h p u = node nt n' h' (Just p) u
275 n' = fromMaybe (defaultName nt) n
276 h' = maybe (defaultHyperdata nt) identity h
278 ------------------------------------------------------------------------
279 node :: (ToJSON a, Hyperdata a, HasDBid NodeType)
286 node nodeType name hyperData parentId userId =
290 (pgNodeId <$> parentId)
293 (sqlJSONB $ cs $ encode hyperData)
295 typeId = toDBid nodeType
297 -------------------------------
298 insertNodes :: [NodeWrite] -> Cmd err Int64
299 insertNodes ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns rCount Nothing
302 insertNodes' :: [Node a] -> Cmd err Int64
303 insertNodes' ns = mkCmd $ \conn -> runInsert_ conn
304 $ Insert nodeTable ns' rCount Nothing
307 ns' = map (\(Node i t u p n d h)
308 -> Node (pgNodeId <$> i)
314 (pgJSONB $ cs $ encode h)
318 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
319 insertNodesR ns = mkCmd $ \conn ->
320 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _ _) -> i)) Nothing)
322 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
323 insertNodesWithParent pid ns = insertNodes (set node_parent_id (pgNodeId <$> pid) <$> ns)
325 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
326 insertNodesWithParentR pid ns = insertNodesR (set node_parent_id (pgNodeId <$> pid) <$> ns)
327 ------------------------------------------------------------------------
329 -- currently this function removes the child relation
330 -- needs a Temporary type between Node' and NodeWriteT
332 node2table :: HasDBid NodeType
333 => UserId -> Maybe ParentId -> Node' -> NodeWrite
334 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)
335 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
338 data Node' = Node' { _n_type :: NodeType
341 , _n_children :: [Node']
344 mkNodes :: [NodeWrite] -> Cmd err Int64
345 mkNodes ns = mkCmd $ \conn -> runInsert_ conn
346 $ Insert nodeTable ns rCount Nothing
348 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
349 mkNodeR ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns (rReturning _node_id) Nothing
351 ------------------------------------------------------------------------
352 childWith :: HasDBid NodeType
353 => UserId -> ParentId -> Node' -> NodeWrite
354 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
355 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
356 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
359 -- =================================================================== --
361 -- CorpusDocument is a corpus made from a set of documents
362 -- CorpusContact is a corpus made from a set of contacts (syn of Annuaire)
363 data CorpusType = CorpusDocument | CorpusContact
367 mk :: HasDBid NodeType => Maybe Name -> Maybe a -> ParentId -> UserId -> Cmd err [NodeId]
369 instance MkCorpus HyperdataCorpus
371 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
372 mk n (Just h) p u = insertNode NodeCorpus n (Just $ DefaultCorpus h) p u
375 instance MkCorpus HyperdataAnnuaire
377 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
378 mk n (Just h) p u = insertNode NodeAnnuaire n (Just $ DefaultAnnuaire h) p u
381 getOrMkList :: (HasNodeError err, HasDBid NodeType)
385 getOrMkList pId uId =
386 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
388 mkList' pId' uId' = maybe (nodeError MkNode) pure . headMay =<< insertDefaultNode NodeList pId' uId'
390 -- | TODO remove defaultList
391 defaultList :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err ListId
393 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
395 defaultListMaybe :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err (Maybe NodeId)
396 defaultListMaybe cId = headMay <$> map (view node_id ) <$> getListsWithParentId cId
398 getListsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataList]
399 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)