2 Module : Gargantext.Database.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 GeneralizedNewtypeDeriving #-}
21 {-# LANGUAGE OverloadedStrings #-}
22 {-# LANGUAGE MultiParamTypeClasses #-}
23 {-# LANGUAGE NoImplicitPrelude #-}
24 {-# LANGUAGE TemplateHaskell #-}
26 module Gargantext.Database.Node where
28 import Data.Text (pack)
29 import GHC.Int (Int64)
30 import Control.Lens (set)
32 import Data.Time (UTCTime)
33 import Database.PostgreSQL.Simple.FromField ( Conversion
34 , ResultError(ConversionFailed)
39 import Prelude hiding (null, id, map, sum)
41 import Gargantext.Core (Lang(..))
42 import Gargantext.Core.Types
43 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
44 import Gargantext.Database.Queries
45 import Gargantext.Database.Config (nodeTypeId)
46 import Gargantext.Prelude hiding (sum)
48 import Database.PostgreSQL.Simple.Internal (Field)
49 import Control.Applicative (Applicative)
50 import Control.Arrow (returnA)
51 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
52 import Control.Monad.IO.Class
53 import Control.Monad.Reader
55 import Data.Maybe (Maybe, fromMaybe)
56 import Data.Text (Text)
57 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
58 import Data.Typeable (Typeable)
60 import qualified Data.ByteString as DB
61 import qualified Data.ByteString.Lazy as DBL
62 import Data.ByteString (ByteString)
64 import Database.PostgreSQL.Simple (Connection)
65 import Opaleye hiding (FromField)
66 import Opaleye.Internal.QueryArr (Query)
67 import qualified Data.Profunctor.Product as PP
69 ------------------------------------------------------------------------
70 ------------------------------------------------------------------------
71 {- | Reader Monad reinvented here:
73 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
75 instance Monad Cmd where
76 return a = Cmd $ \_ -> return a
78 m >>= f = Cmd $ \c -> do
82 newtype Cmd a = Cmd (ReaderT Connection IO a)
83 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
85 runCmd :: Connection -> Cmd a -> IO a
86 runCmd c (Cmd f) = runReaderT f c
88 mkCmd :: (Connection -> IO a) -> Cmd a
91 ------------------------------------------------------------------------
98 ------------------------------------------------------------------------
99 instance FromField HyperdataAny where
100 fromField = fromField'
102 instance FromField HyperdataCorpus where
103 fromField = fromField'
105 instance FromField HyperdataDocument where
106 fromField = fromField'
108 instance FromField HyperdataDocumentV3 where
109 fromField = fromField'
111 instance FromField HyperdataUser where
112 fromField = fromField'
114 instance FromField HyperdataList where
115 fromField = fromField'
117 instance FromField HyperdataAnnuaire where
118 fromField = fromField'
119 ------------------------------------------------------------------------
120 instance QueryRunnerColumnDefault PGJsonb HyperdataAny where
121 queryRunnerColumnDefault = fieldQueryRunnerColumn
123 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument where
124 queryRunnerColumnDefault = fieldQueryRunnerColumn
126 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3 where
127 queryRunnerColumnDefault = fieldQueryRunnerColumn
129 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus where
130 queryRunnerColumnDefault = fieldQueryRunnerColumn
132 instance QueryRunnerColumnDefault PGJsonb HyperdataUser where
133 queryRunnerColumnDefault = fieldQueryRunnerColumn
135 instance QueryRunnerColumnDefault PGJsonb HyperdataList where
136 queryRunnerColumnDefault = fieldQueryRunnerColumn
138 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire where
139 queryRunnerColumnDefault = fieldQueryRunnerColumn
140 ------------------------------------------------------------------------
142 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
143 fromField' field mb = do
144 v <- fromField field mb
147 valueToHyperdata v = case fromJSON v of
149 Error _err -> returnError ConversionFailed field "cannot parse hyperdata"
152 $(makeAdaptorAndInstance "pNode" ''NodePoly)
153 $(makeLensesWith abbreviatedFields ''NodePoly)
156 nodeTable :: Table NodeWrite NodeRead
157 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
158 , _node_typename = required "typename"
159 , _node_userId = required "user_id"
160 , _node_parentId = required "parent_id"
161 , _node_name = required "name"
162 , _node_date = optional "date"
163 , _node_hyperdata = required "hyperdata"
164 -- , node_titleAbstract = optional "title_abstract"
169 nodeTable' :: Table (Maybe (Column PGInt4)
172 ,Maybe (Column PGInt4)
174 ,Maybe (Column PGTimestamptz)
182 ,(Column PGTimestamptz)
186 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
187 , required "typename"
189 , optional "parent_id"
192 , required "hyperdata"
197 queryNodeTable :: Query NodeRead
198 queryNodeTable = queryTable nodeTable
200 selectNode :: Column PGInt4 -> Query NodeRead
201 selectNode id = proc () -> do
202 row <- queryNodeTable -< ()
203 restrict -< _node_id row .== id
206 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
207 runGetNodes q = mkCmd $ \conn -> runQuery conn q
209 ------------------------------------------------------------------------
210 selectRootUser :: UserId -> Query NodeRead
211 selectRootUser userId = proc () -> do
212 row <- queryNodeTable -< ()
213 restrict -< _node_userId row .== (pgInt4 userId)
214 restrict -< _node_typename row .== (pgInt4 $ nodeTypeId NodeUser)
217 getRoot :: UserId -> Cmd [Node HyperdataUser]
218 getRoot userId = mkCmd $ \conn -> runQuery conn (selectRootUser userId)
219 ------------------------------------------------------------------------
221 -- | order by publication date
222 -- Favorites (Bool), node_ngrams
223 selectNodesWith :: ParentId -> Maybe NodeType
224 -> Maybe Offset -> Maybe Limit -> Query NodeRead
225 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
226 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
227 limit' maybeLimit $ offset' maybeOffset $ orderBy (asc _node_id) $ selectNodesWith' parentId maybeNodeType
229 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
230 selectNodesWith' parentId maybeNodeType = proc () -> do
231 node <- (proc () -> do
232 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
233 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
235 let typeId' = maybe 0 nodeTypeId maybeNodeType
237 restrict -< if typeId' > 0
238 then typeId .== (pgInt4 (typeId' :: Int))
240 returnA -< row ) -< ()
244 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
247 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
248 -- deleteNode :: Int -> Cmd' Int
250 deleteNode :: Int -> Cmd Int
251 deleteNode n = mkCmd $ \conn ->
252 fromIntegral <$> runDelete conn nodeTable
253 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
255 deleteNodes :: [Int] -> Cmd Int
256 deleteNodes ns = mkCmd $ \conn ->
257 fromIntegral <$> runDelete conn nodeTable
258 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
261 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
262 -> Maybe Offset -> Maybe Limit -> IO [Node a]
263 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
264 runQuery conn $ selectNodesWith
265 parentId nodeType maybeOffset maybeLimit
269 getNodesWithParentId :: Int
270 -> Maybe Text -> Connection -> IO [NodeAny]
271 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
273 getNodesWithParentId' :: Int
274 -> Maybe Text -> Connection -> IO [NodeAny]
275 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
278 ------------------------------------------------------------------------
279 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
280 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
282 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
283 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
285 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
286 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
288 ------------------------------------------------------------------------
289 selectNodesWithParentID :: Int -> Query NodeRead
290 selectNodesWithParentID n = proc () -> do
291 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
293 then parent_id .== (toNullable $ pgInt4 n)
294 else isNull parent_id
297 selectNodesWithType :: Column PGInt4 -> Query NodeRead
298 selectNodesWithType type_id = proc () -> do
299 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
300 restrict -< tn .== type_id
303 type JSONB = QueryRunnerColumnDefault PGJsonb
305 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
306 getNode conn id _ = do
307 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
309 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
310 getNodesWithType conn type_id = do
311 runQuery conn $ selectNodesWithType type_id
313 ------------------------------------------------------------------------
315 -- TODO Classe HasDefault where
316 -- default NodeType = Hyperdata
317 ------------------------------------------------------------------------
318 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
319 ------------------------------------------------------------------------
320 defaultUser :: HyperdataUser
321 defaultUser = HyperdataUser (Just $ (pack . show) EN)
323 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
324 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
326 name = maybe "User" identity maybeName
327 user = maybe defaultUser identity maybeHyperdata
328 ------------------------------------------------------------------------
329 defaultFolder :: HyperdataFolder
330 defaultFolder = HyperdataFolder (Just "Markdown Description")
332 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
333 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
335 name = maybe "Folder" identity maybeName
336 folder = maybe defaultFolder identity maybeFolder
337 ------------------------------------------------------------------------
338 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
339 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
341 name = maybe "Corpus" identity maybeName
342 corpus = maybe defaultCorpus identity maybeCorpus
343 --------------------------
344 defaultDocument :: HyperdataDocument
345 defaultDocument = hyperdataDocument
347 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
348 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
350 name = maybe "Document" identity maybeName
351 doc = maybe defaultDocument identity maybeDocument
352 ------------------------------------------------------------------------
353 defaultAnnuaire :: HyperdataAnnuaire
354 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
356 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
357 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
359 name = maybe "Annuaire" identity maybeName
360 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
361 --------------------------
362 defaultContact :: HyperdataContact
363 defaultContact = HyperdataContact (Just "Name") (Just "email@here")
365 nodeContactW :: Maybe Name -> Maybe HyperdataContact -> AnnuaireId -> UserId -> NodeWrite'
366 nodeContactW maybeName maybeContact aId = node NodeContact name contact (Just aId)
368 name = maybe "Contact" identity maybeName
369 contact = maybe defaultContact identity maybeContact
370 ------------------------------------------------------------------------
371 defaultList :: HyperdataList
372 defaultList = HyperdataList (Just "Preferences")
374 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
375 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
377 name = maybe "Listes" identity maybeName
378 list = maybe defaultList identity maybeList
380 ------------------------------------------------------------------------
381 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
382 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
384 typeId = nodeTypeId nodeType
385 byteData = DB.pack . DBL.unpack $ encode hyperData
387 -------------------------------
388 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
389 NodePoly (maybe2 Int) Int Int (maybe1 Int)
390 Text (maybe3 UTCTime) ByteString
391 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
392 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
393 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
401 ------------------------------------------------------------------------
402 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
403 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
405 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
406 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
408 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
409 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
410 -------------------------
411 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
412 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
414 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
415 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
416 ------------------------------------------------------------------------
417 -- TODO Hierachy of Nodes
418 -- post and get same types Node' and update if changes
420 {- TODO semantic to achieve
421 post c uid pid [ Node' NodeCorpus "name" "{}" []
422 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
423 , Node' NodeDocument "title" "jsonData" []
428 ------------------------------------------------------------------------
431 -- currently this function remove the child relation
432 -- needs a Temporary type between Node' and NodeWriteT
433 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
434 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
435 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
436 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
439 data Node' = Node' { _n_type :: NodeType
442 , _n_children :: [Node']
446 type NodeWriteT = ( Maybe (Column PGInt4)
449 , Maybe (Column PGInt4)
451 , Maybe (Column PGTimestamptz)
456 mkNode' :: [NodeWriteT] -> Cmd Int64
457 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
459 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
460 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
463 ------------------------------------------------------------------------
465 data NewNode = NewNode { _newNodeId :: Int
466 , _newNodeChildren :: [Int] }
469 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
470 postNode uid pid (Node' nt txt v []) = do
471 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
473 [pid] -> pure $ NewNode pid []
474 _ -> panic "postNode: only one pid expected"
476 postNode uid pid (Node' NodeCorpus txt v ns) = do
477 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
478 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
479 pure $ NewNode pid' pids
481 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
482 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
483 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
484 pure $ NewNode pid' pids
485 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
488 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
489 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
490 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
491 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
494 -- TODO: remove hardcoded userId (with Reader)
495 -- TODO: user Reader in the API and adapt this function
499 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
500 mk c nt pId name = mk' c nt userId pId name
502 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
503 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
505 hd = HyperdataUser . Just . pack $ show EN
509 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
510 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
511 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
512 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
513 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
515 mkRoot :: UserId -> Cmd [Int]
516 mkRoot uId = case uId > 0 of
517 False -> panic "UserId <= 0"
518 True -> mk'' NodeUser Nothing uId ("User Node : " <> (pack . show) uId)
520 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
521 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
523 mkList :: ParentId -> UserId -> Cmd [Int]
524 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]