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 ------------------------------------------------------------------------
234 getNode :: HasNodeError err => NodeId -> Cmd err (Node Value)
236 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
238 Nothing -> nodeError (DoesNotExist nId)
241 getNodeWith :: (HasNodeError err, JSONB a)
242 => NodeId -> proxy a -> Cmd err (Node a)
243 getNodeWith nId _ = do
244 maybeNode <- headMay <$> runOpaQuery (selectNode (pgNodeId nId))
246 Nothing -> nodeError (DoesNotExist nId)
250 ------------------------------------------------------------------------
251 -- | Sugar to insert Node with NodeType in Database
252 insertDefaultNode :: HasDBid NodeType
253 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
254 insertDefaultNode nt p u = insertNode nt Nothing Nothing p u
256 insertDefaultNodeIfNotExists :: HasDBid NodeType
257 => NodeType -> ParentId -> UserId -> Cmd err [NodeId]
258 insertDefaultNodeIfNotExists nt p u = do
259 children <- getChildrenByType p nt
261 [] -> insertDefaultNode nt p u
264 insertNode :: HasDBid NodeType
265 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> Cmd err [NodeId]
266 insertNode nt n h p u = insertNodesR [nodeW nt n h p u]
268 nodeW :: HasDBid NodeType
269 => NodeType -> Maybe Name -> Maybe DefaultHyperdata -> ParentId -> UserId -> NodeWrite
270 nodeW nt n h p u = node nt n' h' (Just p) u
272 n' = fromMaybe (defaultName nt) n
273 h' = maybe (defaultHyperdata nt) identity h
275 ------------------------------------------------------------------------
276 node :: (ToJSON a, Hyperdata a, HasDBid NodeType)
283 node nodeType name hyperData parentId userId =
287 (pgNodeId <$> parentId)
290 (sqlJSONB $ cs $ encode hyperData)
292 typeId = toDBid nodeType
294 -------------------------------
295 insertNodes :: [NodeWrite] -> Cmd err Int64
296 insertNodes ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns rCount Nothing
299 insertNodes' :: [Node a] -> Cmd err Int64
300 insertNodes' ns = mkCmd $ \conn -> runInsert_ conn
301 $ Insert nodeTable ns' rCount Nothing
304 ns' = map (\(Node i t u p n d h)
305 -> Node (pgNodeId <$> i)
311 (pgJSONB $ cs $ encode h)
315 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
316 insertNodesR ns = mkCmd $ \conn ->
317 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _ _) -> i)) Nothing)
319 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
320 insertNodesWithParent pid ns = insertNodes (set node_parent_id (pgNodeId <$> pid) <$> ns)
322 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
323 insertNodesWithParentR pid ns = insertNodesR (set node_parent_id (pgNodeId <$> pid) <$> ns)
324 ------------------------------------------------------------------------
326 -- currently this function removes the child relation
327 -- needs a Temporary type between Node' and NodeWriteT
329 node2table :: HasDBid NodeType
330 => UserId -> Maybe ParentId -> Node' -> NodeWrite
331 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)
332 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
335 data Node' = Node' { _n_type :: NodeType
338 , _n_children :: [Node']
341 mkNodes :: [NodeWrite] -> Cmd err Int64
342 mkNodes ns = mkCmd $ \conn -> runInsert_ conn
343 $ Insert nodeTable ns rCount Nothing
345 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
346 mkNodeR ns = mkCmd $ \conn -> runInsert_ conn $ Insert nodeTable ns (rReturning _node_id) Nothing
348 ------------------------------------------------------------------------
349 childWith :: HasDBid NodeType
350 => UserId -> ParentId -> Node' -> NodeWrite
351 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
352 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
353 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
356 -- =================================================================== --
358 -- CorpusDocument is a corpus made from a set of documents
359 -- CorpusContact is a corpus made from a set of contacts (syn of Annuaire)
360 data CorpusType = CorpusDocument | CorpusContact
364 mk :: HasDBid NodeType => Maybe Name -> Maybe a -> ParentId -> UserId -> Cmd err [NodeId]
366 instance MkCorpus HyperdataCorpus
368 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
369 mk n (Just h) p u = insertNode NodeCorpus n (Just $ DefaultCorpus h) p u
372 instance MkCorpus HyperdataAnnuaire
374 mk n Nothing p u = insertNode NodeCorpus n Nothing p u
375 mk n (Just h) p u = insertNode NodeAnnuaire n (Just $ DefaultAnnuaire h) p u
378 getOrMkList :: (HasNodeError err, HasDBid NodeType)
382 getOrMkList pId uId =
383 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
385 mkList' pId' uId' = maybe (nodeError MkNode) pure . headMay =<< insertDefaultNode NodeList pId' uId'
387 -- | TODO remove defaultList
388 defaultList :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err ListId
390 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
392 defaultListMaybe :: (HasNodeError err, HasDBid NodeType) => CorpusId -> Cmd err (Maybe NodeId)
393 defaultListMaybe cId = headMay <$> map (view node_id ) <$> getListsWithParentId cId
395 getListsWithParentId :: HasDBid NodeType => NodeId -> Cmd err [Node HyperdataList]
396 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)